pax_global_header00006660000000000000000000000064143451464500014520gustar00rootroot0000000000000052 comment=4955c8967e2d76893e395c9ab786747a7625c08f flare-engine-1.14/000077500000000000000000000000001434514645000140015ustar00rootroot00000000000000flare-engine-1.14/.github/000077500000000000000000000000001434514645000153415ustar00rootroot00000000000000flare-engine-1.14/.github/workflows/000077500000000000000000000000001434514645000173765ustar00rootroot00000000000000flare-engine-1.14/.github/workflows/main.yml000066400000000000000000000060431434514645000210500ustar00rootroot00000000000000# This is a basic workflow to help you get started with Actions name: CI # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the master branch push: branches: [ master ] pull_request: branches: [ master ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: compiler: [ { cc: gcc, cxx: g++ }, { cc: clang, cxx: clang++ } ] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - name: Install dependencies run: | sudo apt-get clean -qq sudo apt-get update -qq sudo apt-get install -qq g++ clang libegl1-mesa-dev libgles2-mesa-dev libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev - name: Build id: build continue-on-error: true env: CC: ${{ matrix.compiler.cc }} CXX: ${{ matrix.compiler.cxx }} run: cmake . && make - name: IRC notifications if: github.repository_owner == 'flareteam' && steps.build.outcome != 'success' continue-on-error: true uses: rectalogic/notify-irc@v1 with: server: 'irc.freegamedev.net' port: 6697 channel: '#flarerpg' nickname: 'flareteam-github' message: "${{ github.repository }} (${{ github.sha }}): ${{ matrix.compiler.cxx }} build failed. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - name: Check Status if: steps.build.outcome != 'success' run: exit 1 cppcheck: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - name: Install dependencies run: | sudo apt-get clean -qq sudo apt-get update -qq sudo apt-get install -qq libegl1-mesa-dev libgles2-mesa-dev libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev cppcheck - name: Run cppcheck run: cppcheck --quiet --verbose --enable=all `git ls-files src/\*.cpp` irc_notification: needs: build runs-on: ubuntu-18.04 steps: - name: IRC notifications if: github.repository_owner == 'flareteam' continue-on-error: true uses: rectalogic/notify-irc@v1 with: server: 'irc.freegamedev.net' port: 6697 channel: '#flarerpg' nickname: 'flareteam-github' message: "${{ github.repository }} (${{ github.sha }}): Build passed. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" flare-engine-1.14/.tx/000077500000000000000000000000001434514645000145125ustar00rootroot00000000000000flare-engine-1.14/.tx/config000066400000000000000000000006611434514645000157050ustar00rootroot00000000000000[main] host = https://www.transifex.com [o:flareorg:p:flare-engine:r:data-pot] file_filter = mods/default/languages/data..po source_file = mods/default/languages/data.pot source_lang = en type = PO minimum_perc = 0 [o:flareorg:p:flare-engine:r:engine-pot] file_filter = mods/default/languages/engine..po source_file = mods/default/languages/engine.pot source_lang = en type = PO minimum_perc = 0 flare-engine-1.14/CMakeLists.txt000066400000000000000000000240651434514645000165500ustar00rootroot00000000000000Project (Flare) cmake_minimum_required (VERSION 2.8.12) Set (PACKAGE "FLARE") Set (VERSION "1.14") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") # can't use add_compile_options here. Seems to have something to do with add_compile_options appending it after the "-O3 -DNDEBUG" flags if (MINGW AND CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_CXX_FLAGS_RELEASE "-Wl,-subsystem,windows ${CMAKE_CXX_FLAGS_RELEASE}") endif() # Default definitions if (MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_DEPRECATE) add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE) else () if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-Wno-psabi) endif() add_compile_options( -Wall -Wextra -Wunused -Wshadow -Woverloaded-virtual -Wunreachable-code -Wconversion -Wno-sign-conversion -Wformat-security -fno-math-errno -fno-exceptions -std=c++98 ) endif () add_compile_options( "$<$:-O2>" "$<$:-g0>" "$<$:-O2>" "$<$:-g>" "$<$:-Os>" "$<$:-g0>" "$<$:-O0>" "$<$:-g3>" "$<$:-pg>" ) # Not supported by CMake 3.12! # Travis uses this version so we have to stick to modifying the strings... # add_link_options( # "$<$:-pg>" # ) if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-pg ${CMAKE_EXE_LINKER_FLAGS_DEBUG}") set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "-pg ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}") set(CMAKE_MODULE_LINKER_FLAGS_DEBUG "-pg ${CMAKE_MODULE_LINKER_FLAGS_DEBUG}") endif() set(BINDIR "games" CACHE STRING "Directory from CMAKE_INSTALL_PREFIX where game executable will be installed.") set(DATADIR "share/games/flare" CACHE STRING "Directory from CMAKE_INSTALL_PREFIX where game data files will be installed.") set(MANDIR "share/man" CACHE STRING "Directory from CMAKE_INSTALL_PREFIX where manual pages will be installed.") If(NOT IS_ABSOLUTE "${BINDIR}") set(FLARE_EXECUTABLE_PATH ${CMAKE_INSTALL_PREFIX}/${BINDIR}/flare) Else(NOT IS_ABSOLUTE "${BINDIR}") set(FLARE_EXECUTABLE_PATH ${BINDIR}/flare) EndIf(NOT IS_ABSOLUTE "${BINDIR}") If(NOT IS_ABSOLUTE "${DATADIR}") add_definitions(-DDATA_INSTALL_DIR="${CMAKE_INSTALL_PREFIX}/${DATADIR}") Else(NOT IS_ABSOLUTE "${DATADIR}") add_definitions(-DDATA_INSTALL_DIR="${DATADIR}") EndIf(NOT IS_ABSOLUTE "${DATADIR}") # desktop file If(NOT IS_ABSOLUTE "${BINDIR}") set(FLARE_EXECUTABLE_PATH ${CMAKE_INSTALL_PREFIX}/${BINDIR}/flare) Else(NOT IS_ABSOLUTE "${BINDIR}") set(FLARE_EXECUTABLE_PATH ${BINDIR}/flare) EndIf(NOT IS_ABSOLUTE "${BINDIR}") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/distribution/flare.desktop.in" "${CMAKE_CURRENT_BINARY_DIR}/flare.desktop") # Detect missing dependencies # First, search for SDL2 modules Find_Package(SDL2) If (NOT SDL2_FOUND) Message (FATAL_ERROR "Couldn't find SDL2 development files. On Debian-based systems (such as Ubuntu) you should install the 'libsdl2-dev' package.") Else (NOT SDL2_FOUND) INCLUDE_DIRECTORIES(${SDL2_INCLUDE_DIR}) EndIf (NOT SDL2_FOUND) Find_Package(SDL2_image) If (NOT SDL2IMAGE_FOUND) Message (FATAL_ERROR "Couldn't find SDL2-image development files. On Debian-based systems (such as Ubuntu) you should install the 'libsdl2-image-dev' package.") Else (NOT SDL2IMAGE_FOUND) INCLUDE_DIRECTORIES(${SDL2IMAGE_INCLUDE_DIR}) EndIf (NOT SDL2IMAGE_FOUND) Find_Package(SDL2_mixer) If (NOT SDL2MIXER_FOUND) Message (FATAL_ERROR "Couldn't find SDL2-mixer development files. On Debian-based systems (such as Ubuntu) you should install the 'libsdl2-mixer-dev' package.") Else (NOT SDL2MIXER_FOUND) INCLUDE_DIRECTORIES(${SDL2MIXER_INCLUDE_DIR}) EndIf (NOT SDL2MIXER_FOUND) Find_Package(SDL2_ttf) If (NOT SDL2TTF_FOUND) Message (FATAL_ERROR "Couldn't find SDL2-ttf development files. On Debian-based systems (such as Ubuntu) you should install the 'libsdl2-ttf-dev' package.") Else (NOT SDL2TTF_FOUND) INCLUDE_DIRECTORIES(${SDL2TTF_INCLUDE_DIR}) EndIf (NOT SDL2TTF_FOUND) # Sources Set (FLARE_SOURCES ./src/Animation.cpp ./src/AnimationMedia.cpp ./src/AnimationManager.cpp ./src/AnimationSet.cpp ./src/AStarContainer.cpp ./src/AStarNode.cpp ./src/Avatar.cpp ./src/Camera.cpp ./src/CampaignManager.cpp ./src/CombatText.cpp ./src/CursorManager.cpp ./src/DeviceList.cpp ./src/EffectManager.cpp ./src/EnemyGroupManager.cpp ./src/EngineSettings.cpp ./src/Entity.cpp ./src/EntityBehavior.cpp ./src/EntityManager.cpp ./src/EventManager.cpp ./src/FileParser.cpp ./src/FogOfWar.cpp ./src/FontEngine.cpp ./src/GameSlotPreview.cpp ./src/GameState.cpp ./src/GameStateConfig.cpp ./src/GameStateCutscene.cpp ./src/GameStateTitle.cpp ./src/GameStateLoad.cpp ./src/GameStatePlay.cpp ./src/GameStateNew.cpp ./src/GameSwitcher.cpp ./src/GetText.cpp ./src/Hazard.cpp ./src/HazardManager.cpp ./src/IconManager.cpp ./src/InputState.cpp ./src/ItemManager.cpp ./src/ItemStorage.cpp ./src/Loot.cpp ./src/LootManager.cpp ./src/Map.cpp ./src/MapParallax.cpp ./src/MapCollision.cpp ./src/MapRenderer.cpp ./src/Menu.cpp ./src/MenuActionBar.cpp ./src/MenuActiveEffects.cpp ./src/MenuBook.cpp ./src/MenuCharacter.cpp ./src/MenuConfig.cpp ./src/MenuConfirm.cpp ./src/MenuDevConsole.cpp ./src/MenuEnemy.cpp ./src/MenuExit.cpp ./src/MenuGameOver.cpp ./src/MenuHUDLog.cpp ./src/MenuInventory.cpp ./src/MenuItemStorage.cpp ./src/MenuLog.cpp ./src/MenuManager.cpp ./src/MenuMiniMap.cpp ./src/MenuMovementType.cpp ./src/MenuNumPicker.cpp ./src/MenuPowers.cpp ./src/MenuStash.cpp ./src/MenuStatBar.cpp ./src/MenuTalker.cpp ./src/MenuTouchControls.cpp ./src/MenuVendor.cpp ./src/MessageEngine.cpp ./src/ModManager.cpp ./src/NPC.cpp ./src/NPCManager.cpp ./src/PowerManager.cpp ./src/QuestLog.cpp ./src/RenderDevice.cpp ./src/SaveLoad.cpp ./src/SDLInputState.cpp ./src/SDLSoftwareRenderDevice.cpp ./src/SDLSoundManager.cpp ./src/SDLHardwareRenderDevice.cpp ./src/SDLFontEngine.cpp ./src/Settings.cpp ./src/SharedGameResources.cpp ./src/SharedResources.cpp ./src/SoundManager.cpp ./src/StatBlock.cpp ./src/Stats.cpp ./src/Subtitles.cpp ./src/TileSet.cpp ./src/TooltipData.cpp ./src/TooltipManager.cpp ./src/Utils.cpp ./src/UtilsDebug.cpp ./src/UtilsFileSystem.cpp ./src/UtilsParsing.cpp ./src/Version.cpp ./src/Widget.cpp ./src/WidgetCheckBox.cpp ./src/WidgetButton.cpp ./src/WidgetHorizontalList.cpp ./src/WidgetInput.cpp ./src/WidgetLabel.cpp ./src/WidgetListBox.cpp ./src/WidgetLog.cpp ./src/WidgetScrollBar.cpp ./src/WidgetScrollBox.cpp ./src/WidgetSlider.cpp ./src/WidgetSlot.cpp ./src/WidgetTabControl.cpp ./src/WidgetTooltip.cpp ./src/XPScaling.cpp ./src/main.cpp ) Set (FLARE_HEADERS ./src/Animation.h ./src/AnimationMedia.h ./src/AnimationManager.h ./src/AnimationSet.h ./src/AStarContainer.h ./src/AStarNode.h ./src/Avatar.h ./src/Camera.h ./src/CampaignManager.h ./src/CombatText.h ./src/CommonIncludes.h ./src/CursorManager.h ./src/DeviceList.h ./src/EffectManager.h ./src/EnemyGroupManager.h ./src/EngineSettings.h ./src/Entity.h ./src/EntityBehavior.h ./src/EntityManager.h ./src/EventManager.h ./src/FileParser.h ./src/FogOfWar.h ./src/FontEngine.h ./src/GameSlotPreview.h ./src/GameState.h ./src/GameStateConfig.h ./src/GameStateCutscene.h ./src/GameStateTitle.h ./src/GameStateLoad.h ./src/GameStatePlay.h ./src/GameStateNew.h ./src/GameSwitcher.h ./src/GetText.h ./src/Hazard.h ./src/HazardManager.h ./src/IconManager.h ./src/InputState.h ./src/ItemManager.h ./src/ItemStorage.h ./src/Loot.h ./src/LootManager.h ./src/Map.h ./src/MapParallax.h ./src/MapCollision.h ./src/MapRenderer.h ./src/Menu.h ./src/MenuActionBar.h ./src/MenuActiveEffects.h ./src/MenuBook.h ./src/MenuCharacter.h ./src/MenuConfig.h ./src/MenuConfirm.h ./src/MenuDevConsole.h ./src/MenuEnemy.h ./src/MenuExit.h ./src/MenuGameOver.h ./src/MenuHUDLog.h ./src/MenuInventory.h ./src/MenuItemStorage.h ./src/MenuLog.h ./src/MenuManager.h ./src/MenuMiniMap.h ./src/MenuMovementType.h ./src/MenuNumPicker.h ./src/MenuPowers.h ./src/MenuStash.h ./src/MenuStatBar.h ./src/MenuTalker.h ./src/MenuTouchControls.h ./src/MenuVendor.h ./src/MessageEngine.h ./src/ModManager.h ./src/NPC.h ./src/NPCManager.h ./src/PowerManager.h ./src/QuestLog.h ./src/RenderDevice.h ./src/SDLInputState.h ./src/SDLSoftwareRenderDevice.h ./src/SDLSoundManager.h ./src/SDLHardwareRenderDevice.h ./src/SDLFontEngine.h ./src/Settings.h ./src/SharedGameResources.h ./src/SharedResources.h ./src/StatBlock.h ./src/Stats.h ./src/SoundManager.h ./src/Subtitles.h ./src/TileSet.h ./src/TooltipData.h ./src/TooltipManager.h ./src/Utils.h ./src/UtilsDebug.h ./src/UtilsFileSystem.h ./src/UtilsMath.h ./src/UtilsParsing.h ./src/Version.h ./src/Widget.h ./src/WidgetCheckBox.h ./src/WidgetButton.h ./src/WidgetHorizontalList.h ./src/WidgetInput.h ./src/WidgetLabel.h ./src/WidgetListBox.h ./src/WidgetLog.h ./src/WidgetScrollBar.h ./src/WidgetScrollBox.h ./src/WidgetSlider.h ./src/WidgetSlot.h ./src/WidgetTabControl.h ./src/WidgetTooltip.h ./src/XPScaling.h ) # Add icon and file info to executable for Windows systems IF (WIN32) SET(FLARE_SOURCES ${FLARE_SOURCES} ./src/Flare.rc ) ENDIF (WIN32) Add_Executable (flare ${FLARE_SOURCES} ${FLARE_HEADERS}) # libSDLMain comes with libSDL if needed on certain platforms If (NOT SDL2MAIN_LIBRARY) Set (SDL2MAIN_LIBRARY "") EndIf (NOT SDL2MAIN_LIBRARY) Target_Link_Libraries (flare ${CMAKE_LD_FLAGS} ${SDL2_LIBRARY} ${SDL2IMAGE_LIBRARY} ${SDL2MIXER_LIBRARY} ${SDL2TTF_LIBRARY} ${SDL2MAIN_LIBRARY}) # installing to the proper places install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/flare DESTINATION ${BINDIR}) install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/mods" DESTINATION ${DATADIR}) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/distribution/flare.man" DESTINATION ${MANDIR}/man6 RENAME flare.6) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/flare.desktop" DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/distribution/flare_logo_icon.svg" DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps RENAME flare.svg) flare-engine-1.14/COPYING000066400000000000000000001045131434514645000150400ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. 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 them 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 prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. 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. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey 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; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If 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 convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. 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. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. 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 state 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 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program 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, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU 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. But first, please read . flare-engine-1.14/CREDITS.engine.txt000066400000000000000000000050221434514645000171020ustar00rootroot00000000000000Flare Engine Credits Programming Andrew Engelbrecht | https://n0r.co Anton Golov | http://jesyspa.blogspot.com/ Artur "Zear" Rojek blazindragon bloodhero Bonbadil Chris Oelmueller Clint Bellanger | http://clintbellanger.net David Bariod Daniel Santos Gallaecio Henrik Andersson | https://github.com/hean01 Igor Paliychuk Joseph Bleu Juan Pablo Tamayo "morris989" Justin Jacobs | https://github.com/dorkster Kitano kr4ft3r Kyle Belanger LongerDev Manuel A. Fernandez Montecelo Matthew Krohn | https://github.com/makrohn Nojan Pavel Kirpichyov "Cheshire" r-a-cristian-93 runtime-x86 Ryan Dansie | https://github.com/RyanDansie Stefan Beller | https://github.com/stefanbeller Thane "pennomi" Brimhall | https://github.com/pennomi Distributions Ablu Feillyne Jan-Hendrik (hennr) Peters Manuel A. Fernandez Montecelo miffe ... and many others Translations (be) Belarussian by Mikhail Karalevich and Źmicier Turok (bg) Bulgarian by Emil (ca) Catalan by Marc Tormo i Bochaca and Maria Cano (cs) Czech by Nikita Vanku (Zaraka), hark34 (de) German by Thomas 'CruzR' Glamsch, Chris Oelmueller, Janet Hunt, Stefan Beller, and Wuzzy2 (el) Greek by Yannis Anthymidis, Michael Papageorgiou, and Nea Retrogamer (es) Spanish by Juan Pablo 'morris989' Tamayo, Carlos Sanchez, Diego J. Romero López, and Eric R (fi) Finnish by Timo Sievänen (fr) French by Quentin 'acieroid' Stievenart, Bonbadil, Morgan Strauss, Christoph J. Thompson, syl_, Christophe Nemo, and Bafy (gd) Scottish Gaelic by GunChleoc (gl) Galacian by Adrian Chaves Fernandez (Gallaecio) (hu) Hungarian by bzt (id) Indonesian by Kirim Saja (it) Italian by Giovanni Dalla Torre, Gianfranco Del Borrello, Stefano Peris, Andrea Ranaldi, MOB2, Fabio Loli, and Riccardi Unix (ko) Korean by bnk159hair, YooJin919, ks k (ja) Japanese by Paul Wortmann, sujiniku, Sato Hayato (nb) Norwegian Bokmal by Hans Joachim Desserud, Elias Nykrem (nl) Dutch by Bas Doodeman (pl) Polish by Paweł Puszczyński, Błażej Pęksyk, and Grzegorz Szymaszek (pt) Portuguese by Rui (pt_BR) Brazilian Portuguese by Vicente Monteiro and Rafael Fontenelle (ru) Russian by Sergey Basalaev, Evgen Pavlov, Ademaro, Andrey Kapitonov, David Bell, and berry (sk) Slovak by Miro Jánošík, MiroslavR (sv) Swedish by Andreas Berheim Brudin (uk) Ukrainian by Igor Paliychuk, Denis Lysenko, Микола Франчук, Sergiy Borodych (vi) Vietnamese by Nguyễn Gia Phong (zh) Chinese by lonsine, dumaosen, Issac Zhang Full Credits See the full credits listing including per-file attribution online: https://github.com/flareteam/flare-engine/wiki/Credits flare-engine-1.14/Codingstyle.txt000066400000000000000000000065561434514645000170420ustar00rootroot00000000000000## Contributing Code The #1 rule of this project: _all non-trivial code changes must be discussed first!_ Years of careful decision-making have gone into all parts of this project. Some designs are awaiting planned future changes. If you submit a large patch without knowing how it will affect past and future code, **it will be rejected**. Is my code non-trivial? If it touches multiple functions or files, probably so. Ways to discuss a desired code change: * Open an Issue at https://github.com/flareteam/flare-engine * Email clintbellanger@gmail.com ## Pull Requests When possible, use small pull requests that keep the engine in a stable state. It is far better to have many small pull requests than to wait until your change is too large to merge. If you're working on more than one feature, submit pull requests separately for these. Some times we need to accept one change and reject the other; if your pull request is all combined I have to reject the entire thing. ## Cross-platform considerations To the best of our ability, we want the code to work on Windows, OSX, Linux and more. We try to isolate platform-specific code and use C preprocessor directives when necessary. ## OOP as a last resort My code is simple (some would say not elegant). I start by coding it the simplest possible way that would work. I have a deep disdain for overengineering. Nevertheless Copy&paste coding style should be avoided. Action RPGs are a really simple genre. We don't need fully OOP Entity system to create these games. We have introduced OOP as needed, when the solution makes much more sense than the added complexity. We won't needlessly refactor working code. ## No C++11 / C++0x Maybe we'll allow this kind of code in the future. But Flare has a presence on obscure platforms; I can't guarantee that the compilers have kept up. ## Indentation I try to use tabs. I don't really care though, as long as a single file is internally consistent. ## Naming Conventions Here are the basic conventions, which are not really consistent. Not a big deal, I sometimes clean these up as I go. * ClassName::functionName * ClassName::class_variable * local_variable * ENUM_OR_CONSTANT ## Commenting I try to use Javadoc style comments on functions, especially if they're non-obvious. I try to avoid block comments inside functions (so it's easy to block comment out an entire function). ## Brackets We try to have put brackets in the same line as the adherent command (if, else, while, function) ## Coding style checking in QtCreator In case you're using QtCreator for hacking flare, import the qt.xml file as found in this repository. ## Coding style checking and formatting with artistic style (astyle) There is a script 'astyle.sh', which will automatically format your new code with regard to indentation and brackets. ## Example A piece of code considered good: Classname::function() { // meaningful comment (such as: if player is dead, give no xp for killing monsters) // mind the tabs being equal to 4 spaces: if (variable) { int i = foo(); bar(&i); } else { printf("A fine coding example\n"); } } A bad example Classname::function() { // Having the bracket adding a new line for no reason /* Multiline comments considered bad. */ if(variable) // Mind the missing space between if and opening bracket { int i = foo(); bar(&i); } else { printf("Coding example done wrong\n"); } } flare-engine-1.14/INSTALL.engine.md000066400000000000000000000213421434514645000166770ustar00rootroot00000000000000## Building from Source For easy building I recommend using cmake and make, as it has low overhead when it comes to changes in the code. Since this repository only contains the engine, it is highly recommended that you grab some game data packages (we call them mods). A good place to start would be the official [flare-game] mods. Mods may then be copied to the `mods/` folder manually, if you do not wish to [install Flare system-wide](#install_system_wide). [flare-game]: https://github.com/flareteam/flare-game ### Clone, Build and Play ```sh git clone https://github.com/flareteam/flare-engine.git # clone the latest source code git clone https://github.com/flareteam/flare-game.git # and game data # remember your dependancies(see below) cd flare-engine cmake . make # build the executable cd ../flare-game/mods ln -s ../../flare-engine/mods/default # symlink the default mod cd ../ ln -s ../flare-engine/flare # symlink the executable ./flare # flame on! ``` As a side note, I recommend enabling debugging symbols in order to provide more details if you run into a crash. This can be done by changing the cmake command in the block above to: ``` cmake . -DCMAKE_BUILD_TYPE=Debug ``` You can also build the engine with just [one call to your compiler](#one_call_build) including all source files at once. This might be useful if you are trying to run a flare based game on an obscure platform, as you only need a c++ compiler and the ported SDL package. ## Dependencies To build Flare you need the [2.0 Development Libraries for SDL][libsdl]: SDL\_image, SDL\_mixer, and SDL\_ttf, with the equivalent [2.0 Runtime Libraries][runtimesdl] to run the game; or follow the steps below for your Operating System of choice. [libsdl]: http://www.libsdl.org/download-2.0.php [runtimesdl]: http://www.libsdl.org/download-2.0.php ### Arch Linux Installing dependencies on Arch Linux: ```sh pacman -S --asdeps sdl2 sdl2_image sdl2_mixer libogg libvorbis hicolor-icon-theme python sdl2_ttf ``` There are also AUR PKGBUILDs available for the latest ([engine][arch_dev_engine] and [game][arch_dev_game]) versions. [arch_dev_engine]: https://aur.archlinux.org/packages/flare-engine-git/ [arch_dev_game]: https://aur.archlinux.org/packages/flare-game-git/ ### Debian based systems Installing dependencies on debian based systems (debian, Ubuntu, Kubuntu, etc): ```sh sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev # for development you'll also need: sudo apt-get install cmake make g++ git ``` There is also a [flare build][flare_ubuntu] in the Ubuntu (universe), which actually is flare-game. [flare_ubuntu]:https://packages.ubuntu.com/artful/flare ### Fedora Installing dependencies on Fedora: ```sh sudo dnf install git make cmake gcc-c++ SDL2-devel SDL2_image-devel SDL2_mixer-devel SDL2_ttf-devel ``` ### OpenSuse Installing dependencies on openSUSE: ```sh sudo zypper in make cmake gcc-c++ libSDL2-devel libSDL2_image-devel libSDL2_mixer-devel libSDL2_ttf-devel ``` There is also a flare build at the [openSUSE games repo][suse_repo]. [suse_repo]: https://software.opensuse.org/package/flare ### OS X Installing dependencies using [Homebrew]: ```sh brew install cmake libvorbis sdl2 sdl2_image sdl2_mixer sdl2_ttf ``` [Homebrew]: http://brew.sh/ ### Windows #### MSYS2 / MinGW We use [MSYS2](https://www.msys2.org/) as our official development environment on Windows. ``` # install the build environment with SDL2 libraries # 32-bit pacman -S git mingw-w64-i686-SDL2 mingw-w64-i686-SDL2_image mingw-w64-i686-SDL2_mixer mingw-w64-i686-SDL2_ttf mingw-w64-i686-cmake mingw-w64-i686-gcc mingw-w64-i686-make # 64-bit pacman -S git mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc mingw-w64-x86_64-make git clone https://github.com/flareteam/flare-engine.git cd flare-engine cmake . -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" mingw32-make ``` If you want a debug build, simply replace `Release` with `Debug` as the CMake build type. It is highly recommended that you add `C:\msys64\mingw32\bin` (or `C:\msys64\mingw64\bin` for 64-bit) to your Windows PATH variable for running flare.exe outside of the MSYS environment. To obtain and use the game files, continue from the previous set of commands: ``` cd ../ git clone https://github.com/flareteam/flare-game.git cd flare-game cp -r mods/* ../flare-engine/mods/ ``` #### Microsoft Visual C++ If you want to build flare under Microsoft Visual C++, you should get [dirent.h header file][dirent.h] and copy it to `$MICROSOFT_VISUAL_CPP_FOLDER\VC\include\`. [dirent.h]: https://github.com/tronkko/dirent To get SDL2 [vcpkg](https://github.com/Microsoft/vcpkg) can be used. Its an elegant solution to manage C/C++ dependencies in a central place. Use Windows PowerShell or Git Bash and change to a directory where you want to store the dependencies: ```bash #get the vcpkg code git clone https://github.com/Microsoft/vcpkg.git cd vcpkg #bootstrap vcpkg ./bootstrap-vcpkg.bat #install the dependencies (for 64bit builds, you can use x86-windows triplet instead if you want 32bit) ./vcpkg install sdl2:x64-windows sdl2-image:x64-windows sdl2-mixer:x64-windows sdl2-ttf:x64-windows ``` After that the dependencies have been downloaded and build. You can use them in your cmake projects by adding the path of the toolchain file to your cmake configuration command line options: `-DCMAKE_TOOLCHAIN_FILE=[vcpkg root]\scripts\buildsystems\vcpkg.cmake` ## Install Flare system-wide The executable is called `flare` in this repository or in the flare-game repository, but it is subject to change if you're running another game based on the engine (such as polymorphable). If you want the game installed system-wide, as root, install with: ```sh make install ``` The game will be installed into `/usr/local` by default. You can set different paths in the cmake step, like: ```sh cmake -DCMAKE_INSTALL_PREFIX:STRING="/usr" . ``` ## Building with g++ If you prefer building directly with C++, the command will be something like this: **GNU/Linux** (depending on where your SDL includes are): ```sh g++ -I /usr/include/SDL src/*.cpp -o flare -lSDL2 -lSDL2_image -lSDL2_mixer -lSDL2_ttf ``` **Windows** plus [MinGW]: (Note: Due to an [issue](https://github.com/flareteam/flare-engine/issues/1723#issuecomment-511621267) with compilers that aren't VC++, it is recommended to use SDL\_image 2.0.4) ``` g++ -I C:\MinGW\include\SDL src\*.cpp -o flare.exe -lmingw32 -lSDLmain -lSDL2 -lSDL2_image -lSDL2_mixer -lSDL2_ttf ``` [MinGW]: http://www.mingw.org/ ## Optimizing your build Flare is intended to be able to run on a wide range of hardware. Even on very low end hardware, such as handhelds or old computers. To run on low end hardware smooth, we need get the best compile output possible for this device. The following tips may help improving the the compile output with respect to speed. However these compiler switches are not supported on some platforms, hence we do not include it into the default compile settings. * Make sure the compiler optimizes for exactly your hardware. (g++, see -march, -mcpu) * Enable link time optimisation (g++: -flto) This option inlines small get and set functions accross object files reducing overhead in function calls. * More aggressive optimisation by telling the linker, it's just this program. (g++: -fwhole-program) * to be continued. ## Troubleshooting If the game fails to start, some of the following tips might help: * Open your `settings.txt` file. The location of this file is specified in [README.md](README.engine.md#settings). In that file, set `hwsurface=0` and `vsync=0`. * Older computers might have CPUs that don't support SSE. The default binaries for SDL 2 require SSE. To disable SSE instructions, it is necessary to download the SDL 2 source and build SDL 2 library from source. In case of Windows plus MinGW/MSYS run the following commands from SDL 2 source folder using MSYS terminal: ``` ./configure --disable-sse mingw32-make ``` Then use produced libraries to build flare. In case of Linux use: ``` ./configure --disable-sse make make install ``` Last command will install built libraries system-wide. If you want to build SDL2 from Visual C++ project, open SDL2 project in Visual Studio, go to ``` Project Properties -> C/C++ -> Code Generation -> Enable Enhanced Instruction Set ``` and select No Enhanced Instructions (/arch:IA32). You might really need to rebuild also SDL2\_mixer, SDL2\_image and SDL2\_ttf when you want to use libraries, built with Visual Studio. If you want to build using cmake, use cmake-gui, and uncheck SSE checkbox after executing Configure command. flare-engine-1.14/INSTALL.md000077700000000000000000000000001434514645000203202INSTALL.engine.mdustar00rootroot00000000000000flare-engine-1.14/README.engine.md000066400000000000000000000102661434514645000165310ustar00rootroot00000000000000# Flare Flare (Free Libre Action Roleplaying Engine) is a simple game engine built to handle a very specific kind of game: single-player 2D action RPGs. Flare is not a reimplementation of an existing game or engine. It is a tribute to and exploration of the action RPG genre. Rather than building a very abstract, robust game engine, the goal of this project is to build several real games and harvest an engine from the common, reusable code. The Flare engine, which is purely a runtime, is written in C++. Flare uses simple file formats (INI-style config files) for most of the game data, allowing anyone to easily modify game contents. Open formats are preferred (png, ogg). For creating maps, we also support [Tiled](http://www.mapeditor.org/) with the Flare plugin. Originally the first game to be developed using this engine was part of this repository. As the engine became mature, the game content was moved to an extra repository and is now called [flare-game]. (happened around sept. 2012) [flare-game]: https://github.com/flareteam/flare-game ## Copyright and License Most of Flare is Copyright © 2010-2013 Clint Bellanger. Contributors retain copyrights to their original contributions. Flare's source code is released under the GNU GPL v3. Later versions are permitted. Flare's default mod (includes engine translations) is released under GNU GPL v3 and CC-BY-SA 3.0. Later versions are permitted. The default mod contains the Liberation Sans font which is released under the SIL Open Font License, Version 1.1. The GNU Unifont font is released under GPL v2, with the exception that embedding the font in a document does not in itself bind that document to the terms of the GPL. ## Links The following links are specific to the engine * [Homepage](http://flarerpg.org) * [Source](https://github.com/flareteam/flare-engine) * [Forums](http://opengameart.org/forums/flare) * Email clintbellanger@gmail.com ## Games made with flare The official game from the Flare team, the Empyrean Campaign can be found on the [flare-game](https://github.com/flareteam/flare-game) repository. A collection of other mods produced by the community can be found on the the [Mods](https://flarerpg.org/mods/) page on the offical website. ## Building from Source Flare uses SDL2, SDL2\_image, SDL2\_mixer, and SDL2\_ttf. Please see the [INSTALL.md](INSTALL.engine.md) file for instructions. ## Settings Settings are stored in one of these places: ### Linux/Mac OS X/Unix $XDG_CONFIG_HOME/flare/ $HOME/.config/flare/ ./config ### Windows %APPDATA%\flare\config config\ ### Linux (Snap) $HOME/snap/flare-rpg/current/.config/flare/ ### Linux (Flatpak) $HOME/.var/app/org.flarerpg.Flare/config/flare/ Here you can enable fullscreen, change the game resolution, enable mouse-move, and change keybindings. The settings files are created the first time you run Flare. ## Save Files Save files are stored in one of these places: ### Linux/Mac OS X/Unix $XDG_DATA_HOME/flare/ $HOME/.local/share/flare/ ./userdata ### Windows %APPDATA%\flare\userdata userdata\ ### Linux (Snap) $HOME/snap/flare-rpg/current/.local/share/flare/ ### Linux (Flatpak) $HOME/.var/app/org.flarerpg.Flare/data/flare/ If permissions are correct, the game is automatically saved when you exit. In addition, there is a `mods` directory in this location, which can be used to override system-wide mods. ## Command-line Flags | Flag | Description |-------------------|---------------- | `--help` | Prints the list of command-line flags. | `--version` | Prints the release version. | `--data-path` | Specifies an exact path to look for mod data. | `--debug-event` | Prints verbose hardware input information. | `--renderer` | Specifies the rendering backend to use. The default is 'sdl\_hardware'. Also available is 'sdl', which is a software-based renderer. | `--no-audio` | Disables sound effects and music. | `--mods` | Starts the game with only these mods enabled. | `--load-slot` | Loads a save slot by numerical index. | `--load-script` | Execute's a script upon loading a saved game. The script path is mod-relative. | `--safe-video` | Launches with the minimum video settings. flare-engine-1.14/README.md000077700000000000000000000000001434514645000177762README.engine.mdustar00rootroot00000000000000flare-engine-1.14/RELEASE_NOTES.txt000066400000000000000000002142641434514645000166030ustar00rootroot00000000000000Flare v1.14 Engine features: * Added 'loot.requires_status' and 'status_loot' properties to loot table definitions. * Support '/' as shorthand for 'exec' in the dev console. A space is not required after the slash. * Added 'spawn_level' property to map enemies. This follows the same syntax as the Power property of the same name. * Add XP scaling tables for scaling enemy reward XP based on enemy level. * Replace 'immunity' effects with percentage-based resistances. * Added 'mouse_move_deadzone' property to engine/misc.txt to help alleviate erratic mouse-move behavior when the cursor is near the player. * Alternative tab UI for when tabs exceed the available space. * Added keybind for cycling equipment sets in reverse. * Refactored stats and stat bonuses to use floats for fractional values. * Support fractional values in the following: * Combat text: 'speed' * Engine (combat): 'absorb_percent', 'resist_percent', 'block_percent', 'avoidance_percent', 'miss_damage_percent', 'crit_damage_percent', 'overhit_damage_percent' * Engine (misc): 'party_exp_percentage' * Events: 'power_damage', 'chance_exec' * Death penalty: 'currency', 'xp_total', 'xp_current_level' * Item: 'dmg', 'abs', 'bonus' * Map: 'enemygroup.chance' * Power: 'requires_mp', 'requires_hp', 'requires_hpmp_state', 'attack_speed', 'hp_steal', 'mp_steal', 'missile_angle', 'angle_variance', 'post_effect', 'post_effect_src', 'pre_power', 'modifier_accuracy', 'modifier_damage', 'modifier_critical' * StatBlock: 'stat', 'stat_per_level', 'stat_per_primary', 'vulnerable', 'chance_pursue', 'chance_flee' * Added engine/number_format.txt. This file defines the number of digits to print after the decimal point for fractional values under different contexts. * Add support for multiplicative stat bonuses. For example, an item bonus of 1.5x Max HP, which would be defined as 'bonus=hp,150%'. * As such, the 'hp_percent' and 'mp_percent' stat bonuses are considered deprecated. * Players are directed to the Mods configuration menu via a dialog box when no core mod is enabled. (kr4ft3r) * Core mods are now labeled as such in their tooltips on the Mods configuration menu. * Added 'resource_round_method' property to engine/combat.txt. * Support arbitrary "MP-like" resources: * Configuration file: engine/resource_stats.txt * Added 'requires_resource_stat' and 'resource_steal' properties to Powers. * Added 'requires_resource_stat_state' and 'requires_resource_stat_state_mode' properties to Powers. * Added 'enabled' property to MenuStatBar definitions. Engine fixes: * Fixed set item tooltips showing incorrect number of equipped items if a set item was on an inactive loadout. * Fixed dev console commands not working if they were preceded by whitespace. * Fixed spawning entities not counting towards summoned entity count. * Fix broken gamepad navigation after closing Powers menu while on a non-default tab. * Removed hard-coded minimum values for HP/MP steal and damage return. * Fixed "activate" message incorrectly showing in MenuPowers tooltips. * Fix some cases where 'min' damage/absorb could be greater than the cooresponding 'max' value. * Fixed text input issues with deleting text on Android. Game updates: * Fireball and Ice Bolt scrolls now give the player a 10 second period in which their basic attacks (Swing, Shoot, Channel) are replaced with the scroll power. * Fixed the naming of scroll damage as it appears in power description tooltips. * Lowered Lezaith's slow resistance to 50%, down from 100% * Lowered Lezaith's stun resistance to 90%, down from 100% Translation updates: * German (de) update (Wuzzy2) * Scottish Gaelic (gd) update (GunChleoc) * Indonesian (id) update (Kirim Saja) * Italian (it) update (Riccardi Unix) * Japanese (ja) update (Sato Hayato) * Korean (ko) update (ks k, YooJin919) * Polish (pl) update (Pawonashi) * Russian (ru) update (berry) * Chinese (zh) update (Issac Zhang) Flare v1.13.04 Engine features: * Added 'bar_gfx' property to menus/enemy.txt for setting the "fill" image Engine fixes: * Fix crash when trying to read a non-existent file with INCLUDE * Fix passive powers not being correctly re-applied after death * Improve performance of WidgetListBox by caching all items instead of just the visible ones Flare v1.13 (https://flarerpg.org/2022/01/31/flare-1-13/) Engine features: * Added additional requirement properties (requires_status, requires_item, etc) to enemy and NPC map objects. The properties now match what is available for map events. * When 'mouse aim' is disabled, the Shift key can now be used with the direction keys to change the player's orientation without moving. * Added optional 'fog-of-war' feature (r-a-cristian-93) * Added support for multiple sets/loadouts of equipped items (r-a-cristian-93) * Added random_status attribute to Events for setting/unsetting a random status from a list. * Reworked gamepad handling using the SDL_GameController API. * Several new key bindings, many of which will benefit gamepad users. * Added an "action picker" for interacting with items/powers when using a gamepad or a touchscreen. * Added misc.selection_rect_corner_size to engine/widget_settings.txt. This allows for a nicer "targeting reticle" appearance when selecting widgets without a mouse. * Added 'background' property for all menus. Menus will fall back to the existing hard-coded filenames if this property is omitted. * Added 'close', 'label_title', and 'action_list' properties to MenuConfirm configuration. * Added 'render_layers' section to StatBlock definitions. It has the same syntax and functionality as engine/hero_layers.txt, so that file is now considered deprecated. * Added 'animation_slots' section to StatBlock definitions. This allows for multiple animation layers on non-player entities that don't have an inventory like the player. * Added a 'direction' property to map NPCs, indentical to the property of the same name for map enemies. * Added 'vendor_ratio_buy', 'vendor_ratio_sell', and 'vendor_ratio_sell_old' properties to engine/loot.txt. These deprecate 'vendor_ratio' and 'vendor_ratio_buyback'. * Added 'vendor_ratio_buy', 'vendor_ratio_sell', and 'vendor_ratio_sell_old' properties to NPCs. These can be used to override the global settings in engine/loot.txt. * Added 'tooltip_visible_max' to engine/tootlips.txt to control the maximum number of visible floating tooltips. Engine fixes: * Fix enemies not being able to target NPC followers * Fix loot and sound effects being cleared in StatBlock files with multiple sections * Fix potential crash when removing summons that would be out-of-bounds on the next map. * Fix bug where a damage type with no name could be removed if another damage type was defined after it. * Don't show hidden stats under 'Related Stats' section of tooltips of Character menu. * Fix bug where negative regen wouldn't work if the resource was 100% full. * Fix bug where negative regen would overflow after reaching zero. * Fix bug that caused built-in effect types to be ignored by Power post_effect. * Fix disappearing sprites due to render priority overflow. * Fading of corpses is now correctly disabled when corpse_timeout=0 is used in engine/misc.txt. * Fix cast animation not playing when using an inventory item that is not instant. * Fixed a bug on Android where loading the default settings would exit fullscreen mode. * Fixed crash in NPC dialog when using gamepad. * Fixed several small bugs with navigating UI elements with a gamepad. * Fixed incorrect handling of gamepad stick/trigger deadzone. * Fixed fullscreen setting being turned off when loading configuration defaults on Android. * Fixed input hint for item tooltips when using a touchscreen. * Improved file access handling on Android versions 10 and up. This fixes the infinite cutscene loop when starting a new game. * Fixed memory leak when loading NPCs. * Entity animations will now fall back to the 'die' animation when 'critdie' isn't defined. * Improved text rendering by blending text with a copy of itself. * Fix infinite loop that could occur when setting unlocked powers. * Elemental resistances can now be used with 'stat', 'stat_per_level', and 'stat_per_primary' in StatBlock definitions. Game updates: * New formula for the 'Shield' power. Instead of Mental Damage, it is now based off a combination of player level, Mental stat, and Defense stat. This should increase its usefulness on mixed builds. * Added 'Super' and 'Ultra' variants of Health and Mana potions. They can be obtained through the Alchemy system. * New icons for Health and Mana potions variants. * Fixed Alchemist's Skirt so that it works with all potions. * Prevent the player from damaging Mez before being trapped. * Made Lezaith immune to slow/stun effects. * Goblin Thief can now steal all available kinds of potions. Its drop table has also been expanded to make it easier to replenish lost potions. * Improved contrast for "highlight" state of red square buttons ("close" and "plus"). * The minimap is now gradually revealed as the player explores. * Added icon artwork to the equipment slots on the Inventory menu. Translation updates: * Basque (eu) update (aitzkora) * Scottish Gaelic (gd) update (GunChleoc) * Norwegian Bokmal (nb) update (Elias Nykrem) * Polish (pl) update (Pawonashi) * Russian (ru) update (Igor Polyakov) Flare v1.12 (https://flarerpg.org/2021/08/02/flare-1-12/) Engine features: * NPC interactions are now canceled when the player is attacked. 'combat_aborts_npc_interact' has been added to engine/misc.txt to control this setting. * Added 'bar_fill_offset' and 'bar_fill_size' to MenuStatBar and MenuEnemy properties. * Added support for fading out combat text. See the 'fade_duration' property in engine/combat.txt. * Added 'save_oncutscene' property to engine/misc.txt. Saving before a cutscene was previously controlled with 'save_onload', which caused issues with cutscenes when set to false. * Added 'save_anywhere' property to engine/misc.txt. This allows for saving via the pause menu. (m7600) * Added display of enemies, allies, NPCs, and intermap teleport events to the minimap. * Added support for defining the colors of tiles and entities on the minimap. * Added 'show_on_minimap' event component. Currently, this only applies to intermap teleport events. * Added 'show_on_minimap' property to NPCs. * Properties that take an alignment can now be aligned to the menu frame. * Support multiple images in animation definitions. (bloodhero) * Expanded slot widget configuration in engine/widget_settings.txt. Quantity text color can be changed, as well as label settings for displaying hotkeys. * Re-implemented displaying hotkeys on action bar slots. * Support for 'item:quantity' syntax for Event properties: 'requires_item', 'requires_not_item', 'remove_item', and 'reward_item'. * Added 'show_randomize' property to new game menu configuration. * Added 'disable_equip_slots' property for passive powers. * 'Block' powers can now use post_power to trigger a power when taking damage. * Power descriptions are now displayed in action bar tooltips. This can be controlled with the 'tooltip_length' property in menus/actionbar.txt. * A dialog box is now displayed instead of a log message when the player dies, giving the options to exit or respawn (if applicable). * Added movement input type dialog box. It is displayed for new players to easily select between keyboard, mouse, and joystick movement. * Added '--safe-video' command line option. For troubleshooting purposes, this feature is displayed as an option when starting after a potential crash. * The 'categories' property has been extended to the player character. The Power property 'target_categories' can now be used for the player as a result. * The ability to stash quest items can now be controlled with the 'no_stash' property. * Added a button for opening the pause/configuration menu as part of the minimap. * Added 'script' property to items. This removes the need to create a power in order to execute a script via activating an item. * Player 'melee_range' is now configurable in engine/stats.txt. * Added comparison tooltips for items. These can be disabled in the Interface settings. (r-a-cristian-93, Justin Jacobs) * Added 'parallax_layers' property to Events. This functions exactly like the Map property of the same name. * Added 'visible', 'visible_check_locked', and 'visible_check_status' to powers and upgrades in power trees. * Added 'requires_status' and 'requires_not_status' to powers and upgrades in power trees. 'visible_requires_status' and 'visible_requires_not_status' have been marked as deprecated. * Added 'on_interact' as a possible value for Event types. Essentially the same as 'on_trigger', except the event can ONLY be activated through the hotspot. * Support an arbitrary number of Stash tabs. Each tab has a display name and an 'is_private' flag. * Added 'save_onstash' property to engine/misc.txt, so it is now possible to disable the automatic save when modifying stash contents. * The player's last used Stash tab is now remembered across sessions. * Added the ability for NPCs to join the player's party. (Igor Paliychuk) * Added support for the 'delay' property used by Events contained in script files. * Added 'Frame Limit' option to Video settings. * Added 'Maximum Render Size' option to Video settings. * Improved visual indication of which item set bonuses are active. * Support enabling touch screen controls on non-Android systems. * Added 'Touch Gamepad Scale' option to Input settings. * Support fullscreen mode in the Emscripten port. * Added scrollbar.bg_color to engine/widget_settings.txt. * Updates to package_osx.sh, including usage of Homebrew for dependencies (ludoza) Engine fixes: * Fix memory leak of tab control in stash menu (MiroslavR) * Fixed stat bars not having any fill when the value they were representing was non-zero. Now they will always show at least 1 pixel of the bar's fill graphics. * Fixed a bug where the game window could be resized below the minimum size after exiting fullscreen mode. * Removed the default resolution scaling when virtual_heights is undefined. * Improved performance when checking map event hotspots. * Fix Windows-style paths with backslashes not working on non-Windows systems. * Prevent allies counting as attack targets when using mouse movement. * Fix needing to press the 'Cancel' key twice to exit the vendor menu. * Fix unintended key presses being registered when quitting the game via an OS keyboard shortcut. * Fix divide-by-zero crash when using the dev HUD with a game that uses orthogonal rendering. * Fixed a memory leak in Loot copy constructor. * Fixed 'resist_percent' property of engine/combat.txt not working when targets had 100% resistance. * Support attack_speed effects with a magnitude less than 100%. * Fixed bug where dead entities could steal HP/MP. * Fix broken parsing of engine/default_keybindings.txt. * Fix Effect animations not being synced properly in some cases. * Removed hard-coded restrictions on using items from the inventory. Now any item that has a power and is not equipment can be used. * Fix crash when fonts are missing. The engine will try to fall back to the next available font for the current language. * Background color when using sdl_hardware renderer no longer fills entire window, instead only filling the drawing surface as it should. * Fix crash when loading a cutscene from a book event. * Fix infinite transformation loop when dying in a transformed state that was activated by a passive power. * Fix bug where the player wasn't returned to their normal state after dying in a transformed state. * Prevent combat text from overlapping. * Improved performance when handling many status effects. * Improved performance when an entity is repeatedly unable to find a path to thier target. * Fix bug where killing an enemy with return damage would not set defeat status or reward XP/loot. * Fix check of item requirements when a power requires an item but doesn't consume it. * Fix 'respec' events to no longer remove item-based powers from the action bar. * Fix log message when gaining more than one level at once. * Fixed excessive memory usage when using Items, ItemSets, and Powers with high-value IDs. * Fix bug where power upgrades that didn't require power points would not automatically upgrade if other requirements were met. * Fix infinite loop bug when a power's list of upgrades contains the base power ID. * Fix infinite loop bug when locking power upgrades. * The executable directory is now used when trying to find PATH_DATA on Linux. (r-a-cristian-93) * Fix 'corpse_timeout' in engine/misc.txt being parsed incorrectly (r-a-cristian-93) * Reworked camera speed curve to prevent graphical "wiggle" during slow movement. * Fix missing effect description text in power tooltips when using built-in types. * Fix static events not executing on the same frame as on_load events. * Fix parsing bug in power trees that would save base power properties to an upgrade power. * Fix on_load events with requires_class not working in maps/spawn.txt * Fix timing of Effects so that the first "tick" happens immediately. * Fix "Press button to use" prompt no showing up for some items. * Fix loud loot drop sound effect when there are a large number of drops at once. * Fix incorrect HP/MP regen for high regen values and high frame limits. * Fix camera, parallax layer, and cutscene speeds when using alternative frame limits. * Fix not restoring fullscreen option when pressing Cancel or Defaults in config menu. * Smoother scrolling in WidgetScrollBox. * Fix click-and-drag behavior on WidgetTabControl, WidgetSlider, and WidgetScrollBar. * Fix dialog topics being auto-selected when they shouldn't for non-vendor NPCs. Game updates: * New graphics for HP and MP status bars * Reworked art for the Cleave power * Cleave power radius reduced to closer match its artwork * Reduced the strength and radius of Rakk's freeze attack * Fixed the Goblin Thief being able to steal potions from a Summoned Zombie * Reduced the hitbox for spike traps * Greatly reduced the damage for spike traps. In exchange, the traps now inflict bleeding. * New alchemy ingredient: Mushrooms * Added 4 new potion recipes * Overall reduced drop rates for alchemy items * Added Alchemy quest * Added animations for immunity and ice-based slow status effects * Redesigned the Family Crypt map and quest * Redesigned the Nazia Mines maps and quest * Redesigned the Torture Chambers map and quest (again) * Mortar & Pestle can now be placed on the action bar * All quest items can be stored in the player's private stash. Translation updates: * Belarusian (be) update (Źmicier Turok) * Catalan (ca) update (Maria Cano) * German (de) update (Wuzzy2) * Spanish (es) update (Agustin Ferrario) * Basque (eu) by aitzkora * French (fr) update (leø, Christophe Nemo, Bafy) * Scottish Gaelic (gd) update (GunChleoc) * Hungarian (hu) update (litoll) * Indonesian (id) by Kirim Saja * Korean (ko) by Kor_OfenTHource (team members: bnk159hair, kangdonghun1230, HunSeongPark and paparad0x) * Norwegian (nb) update (Elias Nykrem) * Polish (pl) update (Błażej Pęksyk, Grzegorz Szymaszek) * Portuguese (pt) update (Rui) * Portuguese (Brazilian) (pt_BR) update (Vicente Monteiro) * Russian (ru) update (Igor Polyakov) * Slovak (sk) update (MiroslavR) * Ukranian (uk) update (Sergiy Borodych) * Vietnamese (vi) update (Nguyễn Gia Phong) * Chinese (zh) update (dumaosen, sakura09123) * Chinese (Taiwan) (zh_TW) by RedBug312 Flare v1.11 (http://flarerpg.org/index.php/2019/07/27/flare-1-11/) Engine features: * Players now have their own private stash in addition to the shared stash. * Expanded the 'no_stash' Item variable to account for having multiple stashes. Engine fixes: * Fix bug where items could be dragged from the vendor window and dropped in the opposite vendor tab. * Fix bug that caused item max_quantity to be reset when attempting to append an item definition. * Fix bug where Powers with 'replace_by_effect' would still use properties from the parent power, such as animation state. Game updates: * Items that couldn't be stashed in the last update can now be put in the private stash. * Salted Field: moved chest to the south end of the map to prevent easy farming. Translation updates: * Belarusian (be) update (Zmicer Turok) * German (de) update (Wuzzy2) * Hungarian (hu) by bzt * Ukranian (uk) update (Igor Paliychuk) Flare v1.10 (http://flarerpg.org/index.php/2019/05/17/flare-1-10/) Engine features: * The configuration menu has been redesigned and is now used as the pause menu. * The interface features from version 1.09 have been reverted due to being accessible from the pause menu. * Added an Interface option to disable showing hidden entity markers. * Support floating point values for loot drop chance values. * Add 'hide_timeout' option to stat bar configuration to support auto-hiding. (Leszek Cimała) * Added an Interface option to disable stat bar auto-hiding globally. * Add 'no_stash' property to item definitions to prevent items from being placed in the stash. * Add low HP notification system. (Leszek Cimała) * Add ability to change the minimap zoom level by clicking on the minimap. Engine fixes: * Fixed event activation being tied to distance to the camera position. It is now correctly tied to distance from the player. * Mouse movement and mouse aim now calculate direction relative to the player instead of relative to the screen. * Fixed enemy AI so that enemies can now enter combat if a hero ally is in their proximity. * Fix --data-path command line flag on Windows. * Fixed dialog not being available if it lacked a topic. * Fixed loot drop rate bonus having no effect when loot drop chance was too low. * Fix bug where the wrong requirements were checked for passive powers that had been granted bonus points. * Items that can't be stashed are now dropped on the ground upon opening the stash. * Prevent re-binding the primary Main1 binding, which could break many interactions. * Removed the ability to re-bind the Ctrl, Shift, Alt, and Delete keys, since they correspond to specific keyboard keys. * Fix crash when trying to load a mod that contained 'engine/default_keybindings.txt'. (nwtour) * Display XP stat bar value as relative to current level instead of total XP. (Leszek Cimała) * Fix key bindings not working when switched to a non-US keyboard layout. (nwtour) * Fix segfault when NPC filename is invalid. * Fix inventory tooltips covering books when using no mouse. * Fix navigation of dialog menu when using no mouse. * Fix upgrading powers when using no mouse. * When using the sdl_hardware renderer on Windows, OpenGL will now be used instead of Direct3D. This fixes the loss of some textures when resizing the window. * Fixed the "level up" string to represent the ability to allocate multiple stat points. Also note if Power points can be allocated. * Refactored 'requires_hpmp_state' for Powers to check against both stats at once. See the attribute reference for syntax changes. * Greatly improved performance of loot tooltips. * Fix bug where high item find chance prevented the player from finding currency. Game updates: * Fixed an oversight that allowed easy farming of the Torture Chambers boss and loot chests. * Made it so that potions and scrolls can not be stashed. This fixes an exploit where players could utilize a low level character to get these items for cheap. * Added new attack and interact cursors. * Added sound effect and cursors for indicating low health. Translation updates: * Belarusian (be) update (Zmicer Turok) * Brazilian Portuguese (pt_BR) update (Vinicius Rech) * Catalan (ca) update (Marc Tormo i Bochaca) * German (de) update (Fyrenic) * Spanish (es) update (Agustin Ferrario) * French (fr) update (syl_) * Scottish Gaelic (gd) update (GunChleoc) * Japanese (ja) update (sujiniku) * Portuguese (pt) update (Rui) * Russian (ru) update (Andrey Kapitonov, Igor Paliychuk, nwtour) * Ukranian (uk) update (Igor Paliychuk) * Chinese (zh) update (dumaosen, neverwin) Flare v1.09.01 (http://flarerpg.org/index.php/2018/12/12/flare-1-09-01/) Engine fixes: * Fixed assertion/crash when engaging in dialog. Game updates: * Fixed main weapon layer order when hero is facing east. Translation updates: * Ukranian (uk) update (Микола Франчук) Flare v1.09 (http://flarerpg.org/index.php/2018/12/09/flare-1-09/) Engine features: * Redesigned mouse movement with better handling of non-melee attacks. * New mouse movement options to swap the movement button and/or restrict the movement button to be movement-only. * Loot tooltips can now be hidden when entities are near pieces of loot. The setting "hide_radius" has been added to engine/loot.txt to control this. * Overhead markers are now displayed for entities that are obscured by tall tiles. * Dialog options are no longer shown in a separate menu, but are instead part of the main dialog window. * Added support for dialog responses. For any line of dialog, a list of response topics can be given. * Add 'save_pos_onexit' to engine/misc.txt for saving the player's current position upon exiting. * Added 'colorblind_highlight_offset' and listbox 'text_margin' to engine/widget_settings.txt. * Added a compass to the minimap. * Moved several interface options to the actionbar. Loot tooltips, combat text, statbar labels, and mini-map mode can be adjusted. * Moved the interface option for automatically equipping items to the Inventory menu. * Add 'toggle_fps' to the developer console commands. * Added a 2x zoom mode for the mini-map. * Add a video setting to disable parallax layers. * Add ability to use icons in books. * Add support for shadows on book text via 'text_shadow'. * Add item property 'book_is_readable' to control displaying 'read' vs 'use' in item tooltips. Engine fixes: * Fixed shrinking tab widgets when widow was resized. * Remove broken 'font_fallback' from engine/font_settings.txt. * Add tooltips to language list to show language codes. * Make strings related to elements more translator-friendly. * Fixed a bug where looped map sounds wouldn't play if the player entered the map with a sound volume of 0, but later raised the volume. * Fix buttons and checkboxes not handling clicks while the "Accept" key is pressed. * Fix incorrect descriptions of controls on mobile devices. * Add renderer support for batching calls to local pixel drawing. This speeds up the pre-render of the minimap. * Widgets that use custom images can now fall back to the default image if the custom image is missing (instead of exiting in failure). * Using consumables from the Inventory menu will now subtract from the stack that was clicked on. * Missing items now maintain their stack quantity when moved around. * Fix potential memory leak when redefining images in books. * Fix book open sound effect not being played in all cases. * Closing a book with the 'Cancel' key no longer closes all other menus. * Fix handling of disabled widgets when using keyboard navigation. Game changes: * Fix the "Locked Out" quest not appearing in the completed quests list. * Add interface option buttons to the actionbar. * Add auto-equip checkbox to the Inventory menu. * Added script font for Cyrillic languages. * Made changes to the Wizard's Tower: Laboratory map to prevent attacking Metzger from outside the intended range. New translations: * Catalan (ca) by Marc Tormo i Bochaca Translation updates: * German (de) update (Fyrenic) * Scottish Gaelic (gd) update (GunChleoc) * Brazilian Portuguese (pt_BR) update (Rui) * Portuguese (pt) update (Rui) * Ukranian (uk) update (Igor Paliychuk) * Vietnamese (vi) update (Nguyễn Gia Phong) Flare v1.08 (http://flarerpg.org/index.php/2018/09/17/flare-1-08/) Engine fixes: * Fixed sdl_hardware renderer not clearing top/bottom for tall windows. * Re-create render context when "ignore_texture_filter" setting changes. * Fixed bug where points spent on passive powers would be de-allocated after dying. * Prevent upgrading powers with the '+' button while the player is dead. * Fix off-by-one error when calculating loot drop rates. Translation updates: * German (de) update (Wuzzy2) * French (fr) update (Simon Georges) * Scottish Gaelic (gd) update (GunChleoc) * Italian (it) update (Fabio Loli) Flare v1.07 (http://flarerpg.org/index.php/2018/08/31/flare-1-07/) Engine features: * Added descriptions for some of the player stats to help clarify their purpose. * The game now remembers if the player dismissed the quest log notification. * Navigating cutscenes has been improved. Players can move back and forth through scenes with left/right arrows. * Vertical scrolling cutscenes can be manually scrolled with the Up and Down keys. * Scenes in cutscenes can now pause indefinitely by using "pause=-1". * Added a "lifeform" flag to enemy stats. Setting it to false will display "Destroyed" instead of "Dead" when their HP reaches 0. * Mod descriptions can now be translated with "description_locale". * Added "enable_overlay", "start_at_bottom", and "overlay_at_bottom" to HUD log configuration. * Added "requires_status" and "requires_not_status" to book text. * Added ability for book text to use special variables like "${AVATAR_NAME}". * Added random selection of portrait and class to New Game menu. * Allow setting the class list height in the New Game menu. * Display a list of completed quests in the Quests log. * Item icons can now have an overlay image based on their quality. * Added widget settings for modifying the appearance of the quantity text on item icons. * Added support for playing a sound effect and showing a message when player lacks required MP for power usage. * Added ability to grant bonuses to power levels via items with the "bonus_power_level" property. Engine fixes: * Fixed GetText bug where fuzzy state from header wasn't being unset properly. * Fixed rendering bug in WidgetTabControl. * Fixed crashes when using an empty WidgetListBox. * Fixed GetText bug where strings weren't being sanitized. * Fix locked left/right keys when loading a cutscene from the dev console. * Using the --mods command line flag doesn't overwrite the user's mod configuration. * Fix infinte loop bug when trying to render multi-line text in a small area. * Fix interaction with loot hitboxes interfering with loot tooltips. * Improved handling of mouse movement for melee characters. The player will no longer execute their primary melee attack if they are out of melee range of their target. Also, the player will begin to attack if they enter melee range while moving. * The user keybindings are now reset if the keybindings file is not forwards compatible. Anyone using remapped keys will need to set them again with this update. * Improved performance when dealing with campaign statuses. * Fix NPCs/enemies not begin affected by statuses set in on_load events until the player re-entered the map. * Various fixes to MenuPowers, including better handling of when powers no longer meet their requirements. * Fixed crashes that occurred when NPC did not have animations. * Lots of code cleanup. New translations: * Bulgarian (bg) by Emil * Brazilian Portuguese (pt_BR) by Gabriel Iasi, Vicente Monteiro * Vietnamese (vi) by Nguyễn Gia Phong Translation updates: * Belarusian (be) update (Mikalai Valer'evich Sakalouski, Zmicer Turok) * Czech (cs) update (hark34) * German (de) update (Wuzzy2) * Spanish (es) update (Diego J. Romero López) * Scottish Gaelic (gd) update (GunChleoc) * Russian (ru) update (Sam Vo) * Chinese (zh) update (dumaosen) Game changes: * The player's name can now been seen if they write it in the Book of the Dead. * Fixed not being able to do the Merchant's Journal quest if Abasi was in his final location. * Added color-coded overlays for item icons and improved quantity readability. * Added new sound effects for cursed graves. * Added new sound effect for when the player does not have enough MP to use a power. * Tweaked the bonuses for some unique items to include bonuses to Power levels. * Some small additions have been made to the Hyperspace map. Most notably is a book which can be used to re-watch cutscenes. Flare v1.06 (http://flarerpg.org/index.php/2018/05/23/flare-1-06/) Engine features: * Allow modders to define a target screen DPI for resolution scaling. * Added video option to toggle using DPI scaling. * Add virtual joystick buttons for touchscreen devices. * Add ability to make groups of map enemies invincible based on campaign status. Engine fixes: * Fixed performance flaw when "always show statbar labels" was enabled. * Fix bug where incorrect Power menu tooltip was fetched when not using a mouse. Game changes: * Fix Torture Chambers quest breaking if the player skips the switch at the end. * Fix being able to attack imprisoned enemies in Torture Chambers by making them invincible while the barriers are up. * Reset Torture Chambers quest if it was finished without opening the barriers. Translation updates: * Spanish (es) update (Diego J. Romero López) * Scottish Gaelic (gd) update (GunChleoc) Flare v1.05 (http://flarerpg.org/index.php/2018/04/30/flare-1-05/) Engine features: * Tileset definitions now support multiple images. (Justin Jacobs) * Display a message when unable to use a consumable item from the inventory. (Justin Jacobs) * Add a hint to consumable/book tooltips to expose their interactable nature. (Justin Jacobs) * Add ability to prevent certain powers from being placed on the actionbar. (Justin Jacobs) * Multiple entries of "requires_item" and "requires_equipped_item" are now supported for Powers. (Justin Jacobs) * Add "keep_buyback_on_map_change" to engine/misc.txt to disable saving the buyback between maps. (Justin Jacobs) * Add "reward_loot" and "reward_loot_count" to Events for rewarding via loot tables. (Justin Jacobs) * Support hidding the developer console title label. (Justin Jacobs) * Mod-level default keybinding support added. (Justin Jacobs) * Improved developer mode with inspection cursor. (Justin Jacobs) * Passive powers can now continuously cast their post_power. (Justin Jacobs) Engine fixes: * Fixed incorrect render order for some objects; aka "character partly hidden by front of tent". (Justin Jacobs) * Fix stats/powers not updating when changing equipment in some cases. (Justin Jacobs) * Prevent auto-equipping items if they would cause existing items to be unequipped. (Justin Jacobs) * Fix revive effect not being applied when the player has zero HP. (Justin Jacobs) * Removed unused color key attribute parsing from tileset definitions. (Justin Jacobs) * Fixed building on FreeBSD. (Justin Jacobs) Translation updates: * Spanish (es) update (Eric R) * Scottish Gaelic (gd) update (GunChleoc) * Italian (it) update (MOB2) * Polish (pl) update (Pawonashi) * Portuguese (pt) update (Rui) * Russian (ru) update (Ademaro) * Ukrainian (uk) update (Sergiy Borodych) Flare v1.04 (http://flarerpg.org/index.php/2018/04/19/flare-1-04/) Engine fixes: * Fix a regression that caused book text that was not left-aligned to be cut off. (Justin Jacobs) * Fix a crash when teleporting to the default position of map with a summoned creature. (Justin Jacobs) Game changes: * Fall back to Liberation Sans when fancy fonts lack the appropriate glyphs for the current language. (Justin Jacobs) * Fixed translation script not grabbing subtitles from soundfx/subtitles.txt. (Justin Jacobs) Translation updates: * Scottish Gaelic (gd) update (GunChleoc) * Portuguese (pt) update (Rui) * Ukrainian (uk) update (Sergiy Borodych) Flare v1.03 (http://flarerpg.org/index.php/2018/04/17/flare-1-03/) Engine features: * Buttons that trigger Events can now be added to books. (Justin Jacobs) * Add ability to control image display in books via campaign status. (Justin Jacobs) * Add ability to execute Events upon opening/closing a book. (Justin Jacobs) Engine fixes: * Fix crash when using invalid primary stat in stat_per_primary. (Justin Jacobs) * Fix crash when a book contains an invalid image filename. (Justin Jacobs) * Fix incorrect handling of newlines in tooltips. (Justin Jacobs) * Don't spam "Entity stuck" message while pushing an entity out of a bad tile. (Justin Jacobs) * Keyboard key names such as "Backspace" and "Left Ctrl" can now be translated. (Justin Jacobs) * Prevent HP/MP/XP bar meter from exceeding the size of the bar. (Justin Jacobs) Translation updates: * Scottish Gaelic (gd) update (GunChleoc) * Greek (el) update (Nea Retrogamer) * Spanish (es) update (Diego J. Romero López) * French (fr) update (sang-froid) * Italian (it) update (MOB2) * Portuguese (pt) update (Rui) * Ukrainian (uk) update (Denis Lysenko, Микола Франчук) Flare v1.02 (http://flarerpg.org/index.php/2018/04/05/flare-1-02/) Engine features: * Support multiple campaign statuses for requirements in engine/titles.txt. (Justin Jacobs) Engine fixes: * Fix crash on exit caused by not clearing the Hazard queue. (Justin Jacobs) * Fix regression that caused a crash when configuring mods in the wrong order. (Justin Jacobs) * Fix bug where player title wouldn't update after "unset_status". (Justin Jacobs) * Fix potential NULL dereference in vendor window. (Justin Jacobs) Translation updates: * Scottish Gaelic (gd) update (GunChleoc) * Polish (pl) update (Pawonashi) * Russian (ru) fix (Igor Paliychuk) * Ukranian (uk) update (Микола Франчук) Flare v1.01 (http://flarerpg.org/index.php/2018/03/31/flare-1-01/) Engine features: * Add "Renderer" option to the Video section of the Configuration menu. (Justin Jacobs) * Support loading some hard-coded Configuration menu values from menus/config.txt. (Justin Jacobs) * Add WidgetSettings class for loading previously hard-coded properties of some Widgets. (Justin Jacobs) * Add support for changing the buyback price after the player leaves the map. (Justin Jacobs) * Add ability for certain classes to open a specific tab of the Powers menu automatically. (Justin Jacobs) * Add 'respec' Event component for resetting XP/stats/powers. (Justin Jacobs) * Add 'no_aggro' Power attribute. (Justin Jacobs) Engine fixes: * Fix bug where if minions were blocking the player, they couldn't be moved out of the way. Minions are now forced to move out of the way if the player is close enough. (Justin Jacobs) * Fix bug where removing a primary stat bonus would break passive powers that relied upon said bonus. (Justin Jacobs) * Fix crash on exit when using the sdl_hardware renderer on some systems. (Justin Jacobs) * Fix disappearing text caused be resizing the window in some cases. (Justin Jacobs) * Fix a regression in button and checkbox logic that prevented them from working with keyboard and controller input. (Justin Jacobs) * Fix rendering of tabs when individual tabs are too short or too long. (Justin Jacobs) * Adjust gamma slider range so that the default value is in the middle. (Justin Jacobs) * Clean up error messages that are displayed when the stash hasn't been created. (Justin Jacobs) * Prevent the same key from getting bound to multiple actions. (Justin Jacobs) * Prevent editing joystick bindings when joystick is not in use. (Justin Jacobs) * Don't try to send items to buyback when using SELL_WITHOUT_VENDOR. (Justin Jacobs) * Fix named Effects not showing up in Power menu tooltips. (Justin Jacobs) * Fix passive powers not respecting some power usage requirements. (Justin Jacobs) * Fix some events being activated when they shouldn't if the player was inside of their location rect. (Justin Jacobs) * Fix Power attribute pre_power not working for AI creatures. (Justin Jacobs) * WidgetScrollBox now scrolls in 5% increments based on the size of its contents, as opposed to a hard-coded pixel amount. (Justin Jacobs) * Fix memory leaks in GameSlotPreview, LootManager, PowerManager, and ModManager. (Justin Jacobs) * Fix incorrect dimensions of SVG icon. (Alexander Wilms) * Fix Windows installer not using the correct working directory for launching Flare after install. (Justin Jacobs) Documentation: * Add documentation for creating Mac OS build. (Thomas Leavitt) Translation Updates: * Scottish Gaelic (gd) update (GunChleoc) Game changes: * Redesign final Fort Nasu puzzle, in response to the frustrations of many players. (Justin Jacobs) * Increase elemental ring bonuses by 10%. (Justin Jacobs) * Tweak slingshot stone graphics to make it more visible on some maps. (Justin Jacobs) * Add appdata.xml for use with various package managers. (Alexander Wilms) Flare v1.0 http://flarerpg.org/index.php/2018/03/12/flare-1-0/ Engine changes: * Add dependency checking and metadata to mods; remove --game command line flag (Justin Jacobs) * Engine and mod version checking. Mods can require specific engine & dependency versions. (Justin Jacobs) * Add --mods command line option. (Justin Jacobs) * Allow mod files to "append" their contents instead of replacing the target file (Justin Jacobs) * Print filename & line number when file parsing errors occur (Justin Jacobs) * Switch to using SDL 2 (Justin Jacobs) * Refactored rendering system to support alternate backends (Henrik Andersson, Igor Paliychuk, Justin Jacobs) * Implement automatic frame skipping for slow systems (Justin Jacobs) * Refactor animation system to support higher framerates (Justin Jacobs) * Change default maximum framerate to 60 FPS (Justin Jacobs) * Resizable game window (Justin Jacobs) * Scaled rendering that always fills the window (Justin Jacobs) * Add --renderer command line flag (Justin Jacobs) * Fix engine failing to read fast inputs (Andrew Engelbrect) * Joystick buttons can be re-mapped (Justin Jacobs) * Allow mapping of joystick axes (Justin Jacobs) * Support binding all mouse buttons (Justin Jacobs) * Log console message to file (Justin Jacobs) * Warn when trying to run multiple instances of Flare (Justin Jacobs) * Add --help and --version command line flags (Justin Jacobs) * Add developer console and HUD (Justin Jacobs) * Add book GUI (Igor Paliychuk) * Add GUI for selecting quantity when shift-clicking on items (Justin Jacobs) * Refactor configuration menu code to support alternative platforms (Justin Jacobs) * Improved pause menu with volume sliders. (Justin Jacobs) * Support adding a background image to the minimap. (Justin Jacobs) * Don't autopickup items dropped by the player (Justin Jacobs) * Allow multiple items to drop at the same time (Justin Jacobs) * Missing items due to mod configuration are now marked as "Unknown Item" (Justin Jacobs) * Smoother movement around 1x1 tile objects (Justin Jacobs) * Map rendering performance improvements (Stefan Beller) * Enemies can wander randomly when not in combat (Justin Jacobs) * Better support for using targeted powers when right-clicking on action bar icons (Justin Jacobs) * Implement action queue, allowing potions to be used while attacking (Justin Jacobs) * Added configuration option to always show HP/MP/XP bar text. (Justin Jacobs) * Add option to hide loot labels; show them by pressing ALT (Justin Jacobs) * Prevent loot labels from overlapping each other. (Justin Jacobs) * Refactor AI power system to support unlimited powers instead of just four (Justin Jacobs) * Display bonuses in power tooltips (Justin Jacobs) * Allow more than 4 save slots (Justin Jacobs) * Enemy minions can now receive party buffs. (Justin Jacobs) * Add option to automatically equip items (Justin Jacobs) * The last HUD log message is now displayed on top of open menus that might have previously obscured them. (Justin Jacobs) * "Repeater" powers no longer hit the same target multiple times. (Justin Jacobs) * Status effect icons now have tooltips to show effect name and duration. (Justin Jacobs) * More varied enemy movement patterns with the addition of random fleeing (Justin Jacobs) * Add subtitle support. (Justin Jacobs) * Usability improvements to WidgetInput. (Justin Jacobs) * Interpolate camera movement during intermap/power teleporting. (Justin Jacobs) * Added in-game timer, display time played on load game screen and pause menu. (Justin Jacobs) * Events can now be run from "script" files. (Justin Jacobs) * Add "static" activation type to map events. (Justin Jacobs) * Mouse cursor modding support (Justin Jacobs) * Add ability to save game through Events (Justin Jacobs) * Add Event property to teleport to a random map from a list of maps (Justin Jacobs) * Allow equipped items to disable other equipment slots (Justin Jacobs) * Add support for loot table files (Justin Jacobs) * Randomized item drops and trader inventories via loot tables (Justin Jacobs) * Allow powers to spawn loot (Justin Jacobs) * Added knockback effect (Justin Jacobs) * New stats: Missile Reflect Chance, Damage Reflection (Justin Jacobs) * Allow powers to have no effect on targets with different movement types (Justin Jacobs) * Added ability for hazards to move relative to the caster (Justin Jacobs) * Add Power option to have caster "charge" forward/backward in a straight line (Justin Jacobs) * Support miss & "overhit" damage multipliers. (Justin Jacobs) * The primary stats (Physical, Mental, etc) can now be defined in a mod file. (Justin Jacobs) * Damage types (Melee, Ranged, Mental) can now be defined in a mod file. (Justin Jacobs) * Support grouping active effect icons. (DoKel) * Add "wall_reflect" option for powers. (Clint Bellanger) * Add support for changing attack animation speed. (Justin Jacobs) * Add "color_mod", "alpha_mod", and "blend_mode" options to animations. (Justin Jacobs) * Add parallax map backgrounds/foregrounds. (Justin Jacobs) * Add support for map background color as used in Tiled. (Justin Jacobs) * Icons can now be loaded from multiple image files. (Justin Jacobs) * Added "movie credits"-style scrolling to cutscenes. (Justin Jacobs) * Cutscene improvements: music support and individual scaling of graphics. (Justin Jacobs) * Support for background artwork in main menus (Justin Jacobs) * Vendor buyback can now be stored per-NPC across sessions (Justin Jacobs) * Add MapSaver API (Igor Paliychuk) * Several new properties for powers, effects, events, etc. I forgot to list here to support new game content. (Justin Jacobs) * Mod syntax improvements; generated wiki page defining mod files (everyone) * MANY small bug, crash, and static analysis fixes (everyone) New Ports: * Android port (Igor Paliychuk) * Emscripten port (Justin Jacobs) Translation Updates: * Scottish Gaelic (gd) update (GunChleoc) * Galician (gl) update (Adrián Chaves Fernández) * German (de) update (Janet Hunt) * Italian (it) update (Jeffry84) * Japanese (jp) update (Paul Wortmann, sujiniku) * Norwegian (nb) update (Hans Joachim Desserud) * Polish (pl) update (Pawonashi) * Portuguese (pt) translation (Phillippe Caetano) * Russian (ru) update (Sergey Basalaev) * Swedish (sv) update (Henrik Andersson) Game changes: * Entirely new game to replace the alpha demo, the Empyrean Campaign! Flare v0.19 http://flarerpg.org/blog/20131203 Engine changes: * Power specific stat modifiers (Ryan Dansie) * Cutscenes (Stefan Beller) * Specify data path via command line argument (Justin Jacobs) * Minions support (Ryan Dansie) * Flare engine man page (Christoph Thompson) * Keyboard based menu navigation (Igor Paliychuk, Joseph Bleu, Justin Jacobs) * Movement using floating point positions (Stefan Beller, Justin Jacobs) * Hardcore characters no longer share a stash (Stefan Beller) * Credits added to title menu (Igor Paliychuk) * Additional event triggers (Stefan Beller) * Configurable death penalty (Justin Jacobs) * Colorblind mode options (blazindragon) * Add chance_flee behavior for enemies (Justin Jacobs) * Currency as a stackable item (Justin Jacobs) * Inline configuration attribute documentation (Henrik Andersson) * New minimap tiles for false walls (Matthew Krohn) * Allow custom attack animations (Matthew Krohn) * EventManager to handle map and NPC events (Igor Paliychuk, Justin Jacobs) * Use paths in config files when referring to other files (Justin Jacobs) * Upgradeable powers (Ryan Dansie and Igor Paliychuk) * TONS of internal refactoring/cleanup (everyone) * Various bug fixes (everyone) New Translators: * German (de) update by Wuzzy2 * Greek (el) update by Michael Papageorgiou * Scottish Gaelic (gd) by GunChleoc * Chinese (zh) by lonsine Game Changes: * Gold is now a stackable item and can safely be stored in the shared stash * Heroic Swing and Greater Channel are now upgrades of starting powers * New powers Ember Shot and Thunderstrike Flare v0.18 http://flarerpg.org/blog/20130331 Major Updates: * 10 Equipment Slots, up from 4 (and easily configurable) * Starting "Class" choice (beginner's power/item kit) * Environmental/Ambient Sounds on maps * Much improved handling of Animations, Effects, and Sounds * New Powers: Stealth, Traps, Thrown Weapons * New Item Bonuses: XP gain, Gold Find, Item Find, and more * Improved support for various input devices * Two new starting quests Engine Updates: * Items sold away from vendor are sent to Buyback tab (Justin Jacobs) * Implement avatar layer composition and configurable slots (Igor Paliychuk) * Allow low quality (no alpha) textures for gameplay art (Igor Paliychuk) * Add support for font styles (Justin Jacobs) * Introduce AnimationManager and use it for existing animations (Stefan Beller) * Make buyback price the same as sell price (Justin Jacobs) * Remove many warnings from static code analysis (Stefan Beller) * New starting class system (Justin Jacobs) * Allow assigning titles based on xp, primary stats, or campaign status (Matthew Krohn) * Powers can require HP and optionally kill the caster (Matthew Krohn) * Add on-hit, half dead, and dead power triggers (Justin Jacobs) * New EffectsManager to handle buffs and debuffs (Justin Jacobs) * Add support for Passive Powers (Justin Jacobs) * Add "XP Gain", "Gold Find", "Item Find" bonuses (Justin Jacobs) * Support granting multiple stat/power points per level (Justin Jacobs) * Standardize init lists for classes (Stefan Beller) * Add on_clear map event type (Justin Jacobs) * Add flavor text option for item tooltips (Clint Bellanger) * Convert MenuCharacter to stat listbox (Justin Jacobs) * Allow bonus max HP/MP percentages (Matthew Krohn) * Implement per-enemy loot tables (Justin Jacobs) * Add Stealth bonus effect (Justin Jacobs) * Standardize sound effect loading function (Stefan Beller) * Add internal utility functions (Piotrrak) * Support NPC and map events based on hero level (Stefan Beller) * Allow Accept key to interact with the environment instead of clicking (Justin Jacobs) * Mouse emulation mode (Justin Jacobs) * Add default names for portraits to help keyboardless systems (Justin Jacobs) * Add --game_dataflag for setting a user-defined PATH_DATA (Justin Jacobs) * Introduce SoundManager for centralized sound handling (Henrik Andersson) * Add game_prefix to segregate save games and config files for mods (Henrik Andersson) * Add NPC Action Menu for NPCs with multiple discussion topics (Henrik Andersson) * Add location-based sound volume (Henrik Andersson) * Implement enemy style loot tables for map events (Justin Jacobs) * Make the local data folder prevail over installed ones (Yohann Ferreira) * Rendering optimizations (Stefan Beller) * Allow config file durations to be specified in frames, ms, or seconds (Henrik Andersson) * Fix LMB ignoring locks in some situations (Joseph Bleau) * Add cooldown_hit duration to prevent stunlock (Joseph Bleau) * Add status-based trader stock (Justin Jacobs) * Allow equipment bonuses to be applied while transformed (Justin Jacobs) * Handle fuzzy matching in translation files (Henrik Andersson) * Add item count ranges for loot tables (Clint Bellanger) * Don't allow dead hero to trigger map events (Justin Jacobs) * Add additional cppcheck flags to the .travis.yml file (Hans Joachim) * Clean up BOM from the languages.txt file (Hans Joachim) Game Updates: * Updated equipment from 4 slots (weapon, offhand, body, artifact) to 10 slots (head, body, hands, legs, feet, weapon, offhand, ring, ring, artifact) * Added chainmail and mage armor (Clint Bellanger) * Clothes icon paintings (Ben at CrowLine Studio) * Mage, Leather, Chain, Plate icon paintings (Justin Nichol) * Ring art (Clint Bellanger) * Iron Buckler shield (Clint Bellanger) * Animal Figurines (Clint Bellanger) * Abandoned Tower art and quest line (Stefan Beller) * Mineshaft Longsword quest line (Justin Jacobs) * Beginning of new item list (Clint Bellanger) * Environmental Sounds on existing maps (Henrik Andersson) * New powers: Stealth, Caltrops, Bear Trap, Dagger Throw, Axe Throw (Clint Bellanger) New Translations: * Czech by Nikita Vanku (Zaraka) * Norwegian Bokmal by Hans Joachim Desserud * Polish by Pawel Puszczynski * Many thanks to the other translators who sent in updates Flare v0.17.1 http://flarerpg.org/blog/20121001 Bug Fixes: * Instant-sell items now go to the vendor Buyback tab * Disable New/Load buttons if there is no story mod loaded * Prevent crash when binding higher number mouse buttons * Prevent crash when loading a map with missing layers * Add helpful tooltips when play buttons are disabled * Removed -flto compiler flag from default build script * If you installed v0.17 completely new, you can probably skip this update. Flare v0.17 http://flarerpg.org/blog/20120928 Release Highlights: * All menus now easy to mod/reskin! * New Powers tree with traditional tabs and spendable points * Shared Stash! Collect epics, hoard resist gear, or buff your new characters * Warp Zone -- fast-travel map to get around the alpha demo campaign Code Features: * Menu for showing buffs/debuffs (Justin Jacobs) * Add Texture Quality option (Justin Jacobs) * Add item categories for loot tables (Stefan Beller) * Refactor HP, MP, XP into generic stat bar class (Justin Jacobs) * Convert remaining arrays to vectors where possible (Stefan Beller) * Various refactoring, optimization, cleanup (Stefan Beller) * Move menu options and layouts to config file (Justin Jacobs, Igor Paliychuk, Stefan Beller) * Move various magic numbers to config file (everyone) * Refactor Powers Menu to tab/tree/points style (Igor Paliychuk) * "I shouldn't be coding at 3:30 AM" (Clint Bellanger) * Refactor menus to use base class for placement/size/etc (Justin Jacobs) * Powers that spawn multiple enemies (Matthew Krohn) * Add Shared Stash (Justin Jacobs) * Add a Buyback tab to vendors (Justin Jacobs) * Allow NPCs to heal the hero during dialog (Igor Paliychuk) * Configurable font colors (Justin Jacobs) * Allow enemies to wander in an area (Justin Jacobs) * Enable scroll wheel in some widgets (Justin Jacobs) * Add on_load map event type * Don't load sound/music if the volume is 0 (Stefan Beller) * Added pixel-precision to clickable tiles (Justin Jacobs) * Made equipment slots configurable (Igor Paliychuk) * Made bonuses work on any item type/slot * Map Rendering optimizations (Stefan Beller) * Animation refactoring (Stefan Beller) * Sprite sheet packing and utility (Stefan Beller) * Add support for item sets (Igor Paliychuk) * Add optional Fringe and Foreground tile layers (Matthew Krohn) * Add load screen to map transitions (Justin Jacobs) * FPS display option (Igor Paliychuk and Justin Jacobs) * Configurable buff/debuff animations (Justin Jacobs) * Split flare into two repos: flare-engine for core upstream dev, flare-game for the actual game * Support multi-line text in our GetText implementation (Gallaecio) * Updates to GetText data extractor (Chris Oelmueller, Justin Jacobs, Igor Paliychuk) * Remove corpses after a fixed amount of time, for performance (Justin Jacobs) * Optimize text display in several places (Justin Jacobs) Art Features: * Use full alpha transparency for the hero art * Updated Tiled files to use v0.9 features * Added spike traps * Added double doors, stairs, and bones to the Dungeon tile set * Added ambient sound near braziers * New 320x240 downscale mod * Added a default mod (engine translations and barebones UI) * Added Teleporter art and a warp zone map * Added a lifesteal melee attack, a manasteal magic attack, and a rapid fire bow attack * Updated floors on all the old dungeon maps * Updated Liberation Sans font to 2.0 Translations: * Greek - Yannis Anthymidis * Italian - Giovanni Dalla Torre * Dutch - Bas Doodeman * German - Chris Oelmueller * Galician - Gallaecio * Russian - Sergey Basalaev * Finnish - Timo Sievanen * French - Morgan Strauss Flare v0.16 http://flarerpg.org/blog/20120707 "Advanced Enemies" Release Highlights: * Improved enemy pathfinding, including flying creatures * Summon or transform into enemies! * Config menu including keybindings, mods, and more * Orthogonal map support Code Features: * A* Pathfinding (Nojan) * Enemies now block movement (Clint Bellanger) * Enemies, powers, and map events can spawn enemies (Clint Bellanger) * Floating Combat Text (Thane Brimhall) * New Widgets: tabs, check boxes, scroll bars, sliders, and more (Gallaecio, David Bariod, Justin Jacobs) * Config Menu, including keybindings and mods (David Baiod, Justin Jacobs, Igor Paliychuk) * Flying Creatures (Clint Bellanger) * Patrolling Enemies (Thane Brimhall) * Transformation/Shapeshifting into any creature (Igor Paliychuk) * Configurable XP per level and per creature (Thane Brimhall) * Configurable sell price for items (Justin Jacobs) * Orthogonal Map Support (Thane Brimhall) * Powers can target neighboring squares (Justin Jacobs) * Intramap teleportation events (Clint Bellanger) * Optional Permadeath (Thane Brimhall) * NPCs can be both Talkers and Vendors (Justin Jacobs) * Animated Tiles (Igor Paliychuk) * Gold Autopickup (Justin Jacobs) Art Features: * Widget art via RPG GUI Construction Kit (Lamut) * Wyvern art (Clint Bellanger and Justin Nichol) * Wyvern sounds (Brandon Morris) * Antlion and Zombie summoning animations (Clint Bellanger) * New Minotaur animations (Lamut) * Cursed Grave creature (Clint Bellanger) * Female Merchant Vocals (Holly Daniel) * Creepy Forest theme music (Brandon Morris) * Ancient Temple map (Clint Bellanger) * Full set of Kickstarter portraits (Justin Nichol) Fixes: * Handle systems without sounds (Justin Jacobs) * Joystick handling fixes (Justin Jacobs, Igor Paliychuk) * Multi-line gettext support (Gallaecio) * Movement around corners (Daniel Santos) * Disable unusable powers in the action bar (Daniel Santos) * Multiple Event Components per line for Tiled compatibility (Thane Brimhall) * Unspent attribute points shown on the Character Menu (Igor Paliychuk) * Threat range and entering combat fixes (Justin Jacobs) * Close vendor bag when closing inventory (Justin Jacobs) * Faster previews on Load Game screen (Justin Jacobs) * Isometric Rendering optimizations (Stefan Beller) * Misc fixes (AI0867, AMDmi3, CmPons, manuelafm, runtime-x86, matthiaskrgr) Translations: * French (Morgan Strauss) * German (Thomas Glamsch and Chris Oelmueller) * Italian (Giovanni Dalla Torre and Andrea Ranaldi) * Russian (Sergey Basalaev) * Slovak (Miro Janosik) * Ukranian (Igor Paliychuk) Flare v0.15 http://flarerpg.org/blog/20111223 "Translations and Mods" Release Highlights: * Now using TTF for fonts * All game data can be overwritten/added via mods * Translation support for the core engine and mods * New Grassland tileset * New questing areas: Frontier and Living Bones * Redesigned creatures are tougher and more varied Contributions: * Thane Brimhall "pennomi" - Living Bones mod and plenty of code * Manuel A. Fernandez Montecelo - code and Debian/Ubuntu packaging * Igorko: code and Win build features * Hennr - Debian/Ubuntu packaging * Remaxim - composer of Magical Theme * Brandon Morris - steps foley, new level up sound * Justin Nichol - character portraits * Stefan Beller - Tiled flare export mod and automapping help * Thorbjorn Lindeijer - Tiled tileset offset feature * AMDmi3 - Build help * acieroid - Display power cooldown timer and various mouse/menu fixes Translations changes: * (by) Belarusian by mikhailSaTuRn * (de) German by Thomas 'CruzR' Glamsch * (es) Spanish by Juan Pablo 'morris989' Tamayo * (fi) Finnish by Timo SievA¤nen * (fr) French by acieroid and Bonbadil * (gl) Galacian by Gallaecio * (ja) Japanese by Paul Wortmann * (ru) Russian by Sbasalaev * (sv) Swedish by Andreas Berheim Brudin * (uk) Ukrainian by igorko Flare v0.14.1 http://flarerpg.org/blog/20110713 BugFix: * It patches a significant memory leak in the v0.14 release. Flare v0.14 http://flarerpg.org/blog/20110707 "Main Menus" ChangeLog: * Title Screen added * Load Game Screen added with four game slots * New Game Screen added with choosable portrait and hero name * Title Music by Brandon Morris * Customizeable portraits with matching sprite heads for heroes * Player portrait displayed during NPC conversations * Female heroine option added * Six player portraits added by Justin Nichol * Save and Exit to Main Menu confirmation added by kitano * GameState base class by kitano * Entity base class by kitano * Variable MP cost for spells added by kitano * Animation class added by kitano * WidgetButton generalized by kitano * Arrow and X to show conversation progress added by kitano * Input text widget added by kitano * Clickable map events added by Thane "pennomi" Brimhall * Enemy group random spawning added by Thane "pennomi" Brimhall * Freeze and Multishot code expanded by Thane "pennomi" Brimhall * Damage Multiplier for powers added by Thane "pennomi" Brimhall * Life Steal and Mana Steal for powers added by Thane "pennomi" Brimhall * Power Cooldowns added by Thane "pennomi" Brimhall and wokste * Item bonus to increase base stats added by Alexander Olkhovskiy * Enabled multiple bonuses on items (up to 8) * Joystick support added by Artur "Zear" Rojek * FileParser now used by all data files added by Bonbadil * Added close buttons to several menus * Attempts to use standard *nix directories if available (XDG or $HOME) * Sound effects volume added to settings.txt * Double Buffering option added to settings.txt * Hardware Surfaces option added to settings.txt * Bug Fix: Teleport Scrolls can no longer be used without a target * Bug Fix: Windows mkdir fixed by Paul-Wortmann Flare v0.13 http://flarerpg.org/blog/20110502 "Quests" Release Highlights: * Flare now supports quests! * NPC questgivers can send players on quests and give rewards on completion. * Quests can also involve finding items, killing bosses, or exploring a map area. Detailed list of changes: * New "Lost Mines" area by Thane Brimhall (pennomi) * MenuTalker code beginnings by Juan PabloTamayo (morris989) * Added GameSwitcher class in preparation for Main Menus * Block power buffed * Framerate fixed. Animations adjusted and should be smoother on low-power systems. * Added healing-over-time support for powers. * New stat awards on level up. * Added new potion levels * "Magical" renamed "Mental" for genre neutrality * Rearranged power tree. All power slots now implemented * New "Channel" spell: short-range spell that costs 0 mana but requires a Mental weapon * New "Immobilize" power: use a ranged weapon to pin a creature to the ground * New config files to edit resolution, fullscreen, keybindings * Updated health bar by Scrittl * Stackable items and inventory refactoring by Bonbadil * Basic conversations added * NPC portrait by Justin Nichol * Campaign Manager added to handle quest states * Enemies no longer use ranged powers when losing line of sight * Added consumable spell scrolls by Thane Brimhall (pennomi) * Added elemental spell scroll icons * Separated icons into 32px and 64px pages * Added NPC ability to advance quest and give rewards * Added monster kill to advance quests * Added quest loot for monsters * Added a tutorial NPC * Added on-pickup quest advancement on items * Added Averguard Keep quest chain featuring 4 bosses and 3 quest items * Added a quest-giver NPC * Added key and book icon for quests * Added tabbed interface to Log Menu * Log Menu has room for Quests, Messages, Statistics, and Achievements * Weapon requirements displayed on Power tooltips * (bugfix) Enemies no longer run before changing directions when entering combat * (bugfix) Enemies leave combat when the player is dead * (bugfix) Missing menu deconstruction calls added * (bugfix) Correct memory reallocation when chaning equipment * (bugfix) Correct resist stats on bows Flare v0.12 http://flarerpg.org/blog/20110315 "Vendors" ChangeLog: * Vendors have been added. Buy and sell items! There is one vendor at the spawn point and another vendor in the cave. * Voice-acting for the vendor by Brandon Morris (Augmentality) * Male vendor 3D model by TiZiana * Potions can be placed on the Action Bar. The number of remaining potions is shown. When you run out of that potion, the icon goes dark. Thanks to help from LongerDev. * Support for other powers on items has been added. This opens up the possibility for interesting items in the future (e.g. teleport boots). * Action is no longer paused when menus are open. This behavior can be changed in Settings.h. Contributed by LongerDev * Basic support for Mouse Movement added. Edit your save file and you'll see a mouse_move option. Contributed by Cheshire. * Mouse-over on creatures shows a menu with the monster's name, level, and health. Contributed by Cheshire. * Added a basic cmake file to the source distributable. Also rearranged the folder structure to make things easier for novice users. Thanks to gnurobert and ceninan for help on this! * Dying now has a penalty: 50% gold loss * Crits are more powerful and always do more damage than a regular attack. Flare v0.11 http://flarerpg.org/blog/20110228 "Enemy Powers" Changes: * Enemies have four slots for powers. New enemies shoot bows, cast spells, throw javelins, and more! * Most powers moved to a config file. * New: Cave level (head east from the Goblin Warrens. Creatures are level ~6) * New creature art: skeletal archer, skeletal mage, fire antlion, ice antlion, antlion hatchling * Some magical ranged weapons have Ice and Fire Ammo... * Various bug fixes and new minor features Special thanks to these contributors: * Brandon Morris composed the Cave theme * Thane "pennomi" Brimhall scripted the Cave enemies and events * Gordon "mogrohl" patched the looting-while-dead bug * ferrer patched uninitialized music variables * BartK contributed new zombie sounds ("Braaaains!") OSARE v0.10 http://flarerpg.org/blog/20101025 "Powers" New in this version: * Magic, melee, and ranged powers * Action bar support for powers * Click-to-activate action bar and menus * XP bar on HUD * Minimap on HUD * New health/mana bar images * New creature: Minotaur * README.txt to help new players OSARE v0.09 http://flarerpg.org/blog/20100905 ChangeLog: * Games are automatically saved upon exit. * Level Up system in place! * You must meet requirements to equip items now * Now you can respawn after dying * Gold added to loot drops! Though nothing to spend it on yet... * New map "Averguard Complex" added! OSARE v0.08 http://flarerpg.org/blog/20100901 "Loot" * The newest OSARE release is focused on loot. * Over 500 items are in the game, dropped from enemies or found in treasure chests. * I also added the starting logic for the Log Menu (and a heads-up log display). * Finally, there is a new starting area called "Goblin Warrens" where you can fight low-level creatures to scavenge for beginner equipment. OSARE v0.07 http://flarerpg.org/blog/20100611 "Inventory" New in this release: * Word wrap, size calculation, and font colors on the font engine * Created a mouseover tooltip system * Drag and drop support for arranging and equipping items * Item drop sound effects * Changing hero graphics based on equipped items * Changing combat stats based on equipped items * Adjusted monsters based on combat stats * Basic test items and a few low and high quality variations OSARE v0.06 http://flarerpg.org/blog/20100528 New in this release: * Tweaked some sound effects due to license questions * Added menu open/close sound effects * Added map interactions when stepping on certain tiles * Added opening containers * Added throwing switches to open doors * Added support for various monster types * Added several varieties of zombie, skeleton, antlion, and goblin OSARE v0.05 http://flarerpg.org/blog/20100516 ChangeLog: * Added map transitions * Added a starter dungeon with three wings named Averguard Hold (click for map) * Added support for hex or dec map data format * Changed definition files to use = instead of : * Upgraded zombie steering AI. They now pursue to where they last saw the hero. * Started refactoring to prepare for more monster varieties * EnemyManager now holds enemy sound effects, so that we only load one copy of each * I now use Tiled 0.4.1 to edit map layers * Reminder: use the command-line argument -f (or --fullscreen) to play in fullscreen mode. * Changed the tileset to use magic pink as the transparency and removed the tileset alpha layer. This should improve performance on most systems. * Note to self: in GIMP set Alpha Threshold to 32, then remove the alpha channel onto magic pink. OSARE v0.04 http://flarerpg.org/blog/20100414 "Font Support and Basic Menus" Features: * NOTE: now runs in windowed mode by default (apologies to XP users, windowed mode seems to change the desktop resolution). To run in fullscreen mode use -f or --fullscreen options at command line (easier to use options coming in a future release) * FontEngine is a class that handles drawing of a bitmap font, with support for left/center/right justify (word wrap coming in a future release) * Finished the icons for Powers. Commissioned Blarumyrran to finish the icons for the basic set of weapons/armors. * Character Menu displays the proper info based on the Avatar's stats (level up coming in a future release) * Powers Menu displays the Avatar's build (dragging powers to the action bar coming in a future release) * Log and Inventory menus are mostly static. Inventory drag/drop and equipping coming later. I still haven't settled on a design for the Log, which will contain quest info and achievements. * I refactored all the menus and hud under a MenuManager class. That class controls which menus should be displayed. In a future release it will handle drag/drop between the Inventory, Powers, and ActionBar menus. * When the core menus are open (Character, Log, Powers, Inventory) the game action is paused. * The actionbar hotkeys work but clicking on the action bar doesn't work yet. Also, still no way to change the default key bindings. All this coming in future releases. OSARE v0.03 http://flarerpg.org/blog/20100228 ChangeLog: * Combat to the death. You can kill zombies and they can kill you. * Added combat sound effects. * Added a health and mana bar * Left-mouse click is now an aimed sword attack (1-key will instead attack the way you're currently facing) * Memory cleanup in the class deconstructors. OSARE v0.02 http://flarerpg.org/blog/20100215 ChangeLog: * Re-rendered all assets using Blender's CatRom AA setting. The result is much sharper. * Added SDL_mixer support and background music by Remaxim * Added the bare bones for the Action Bar * Set Action Bar slot 1 to melee attack (no customization yet) * Added (much hardcode) a basic zombie creature. The zombie will pursue and try to melee attack. * Bug: fixed an issue in the Collider class with a variable not initialized * Background music * The hero and zombie can try to attack each other, I haven't yet implemented the attack collisions, damage, dying, etc. OSARE v0.01 http://flarerpg.org/blog/20100101 flare-engine-1.14/cmake/000077500000000000000000000000001434514645000150615ustar00rootroot00000000000000flare-engine-1.14/cmake/Copyright.txt000066400000000000000000000052251434514645000175760ustar00rootroot00000000000000CMake - Cross Platform Makefile Generator Copyright 2000-2014 Kitware, Inc. Copyright 2000-2011 Insight Software Consortium Copyright 2014 Justin Jacobs All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the names of Kitware, Inc., the Insight Software Consortium, nor the names of their contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------ The above copyright and license notice applies to distributions of CMake in source and binary form. Some source files contain additional notices of original copyright by their contributors; see each source for details. Third-party software packages supplied with CMake under compatible licenses provide their own copyright notices documented in corresponding subdirectories. ------------------------------------------------------------------------------ CMake was initially developed by Kitware with the following sponsorship: * National Library of Medicine at the National Institutes of Health as part of the Insight Segmentation and Registration Toolkit (ITK). * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel Visualization Initiative. * National Alliance for Medical Image Computing (NAMIC) is funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149. * Kitware, Inc. flare-engine-1.14/cmake/FindSDL2.cmake000066400000000000000000000135501434514645000173740ustar00rootroot00000000000000# - Locate SDL2 library (modified from CMake's FindSDL.cmake) # This module defines # SDL2_LIBRARY, the name of the library to link against # SDL2_FOUND, if false, do not try to link to SDL # SDL2_INCLUDE_DIR, where to find SDL.h # SDL2_VERSION_STRING, human-readable string containing the version of SDL2 # # This module responds to the the flag: # SDL2_BUILDING_LIBRARY # If this is defined, then no SDL_main will be linked in because # only applications need main(). # Otherwise, it is assumed you are building an application and this # module will attempt to locate and set the the proper link flags # as part of the returned SDL2_LIBRARY variable. # # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration # and no SDL2_LIBRARY, it means CMake did not find your SDL2 library # (SDL2.dll, libsdl2.so, SDL2.framework, etc). # Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. # Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value # as appropriate. These values are used to generate the final SDL2_LIBRARY # variable, but when these values are unset, SDL2_LIBRARY does not get created. # # # $SDLDIR is an environment variable that would # correspond to the ./configure --prefix=$SDLDIR # used in building SDL. # #============================================================================= # Copyright 2000-2014 Kitware, Inc. # Copyright 2000-2011 Insight Software Consortium # Copyright 2014 Justin Jacobs # All rights reserved. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= find_path(SDL2_INCLUDE_DIR SDL.h HINTS ENV SDLDIR PATH_SUFFIXES include/SDL2 include ) find_library(SDL2_LIBRARY_TEMP NAMES SDL2 HINTS ENV SDLDIR PATH_SUFFIXES lib ) if(NOT SDL2_BUILDING_LIBRARY) if(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") # Non-OS X framework versions expect you to also dynamically link to # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms # seem to provide SDLmain for compatibility even though they don't # necessarily need it. find_library(SDL2MAIN_LIBRARY NAMES SDL2main HINTS ENV SDLDIR PATH_SUFFIXES lib PATHS /sw /opt/local /opt/csw /opt ) endif() endif() # SDL2 may require threads on your system. # The Apple build may not need an explicit flag because one of the # frameworks may already provide it. # But for non-OSX systems, I will use the CMake Threads package. if(NOT APPLE) find_package(Threads) endif() # MinGW needs an additional library, mwindows # It's total link flags should look like -lmingw32 -lSDLmain -lSDL -lmwindows # (Actually on second look, I think it only needs one of the m* libraries.) if(MINGW) set(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") endif() if(SDL2_LIBRARY_TEMP) # For SDLmain if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY) list(FIND SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" _SDL2_MAIN_INDEX) if(_SDL2_MAIN_INDEX EQUAL -1) set(SDL2_LIBRARY_TEMP "${SDL2MAIN_LIBRARY}" ${SDL2_LIBRARY_TEMP}) endif() unset(_SDL2_MAIN_INDEX) endif() # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa. # CMake doesn't display the -framework Cocoa string in the UI even # though it actually is there if I modify a pre-used variable. # I think it has something to do with the CACHE STRING. # So I use a temporary variable until the end so I can set the # "real" variable in one-shot. if(APPLE) set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") endif() # For threads, as mentioned Apple doesn't need this. # In fact, there seems to be a problem if I used the Threads package # and try using this line, so I'm just skipping it entirely for OS X. if(NOT APPLE) set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) endif() # For MinGW library if(MINGW) set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) endif() # Set the final string here so the GUI reflects the final state. set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") # Set the temp variable to INTERNAL so it is not seen in the CMake GUI set(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") endif() if(SDL2_INCLUDE_DIR AND EXISTS "${SDL2_INCLUDE_DIR}/SDL_version.h") file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MINOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_INCLUDE_DIR}/SDL_version.h" SDL2_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_PATCHLEVEL[ \t]+[0-9]+$") string(REGEX REPLACE "^#define[ \t]+SDL_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MAJOR "${SDL2_VERSION_MAJOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_MINOR "${SDL2_VERSION_MINOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_VERSION_PATCH "${SDL2_VERSION_PATCH_LINE}") set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH}) unset(SDL2_VERSION_MAJOR_LINE) unset(SDL2_VERSION_MINOR_LINE) unset(SDL2_VERSION_PATCH_LINE) unset(SDL2_VERSION_MAJOR) unset(SDL2_VERSION_MINOR) unset(SDL2_VERSION_PATCH) endif() include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR VERSION_VAR SDL2_VERSION_STRING) flare-engine-1.14/cmake/FindSDL2_image.cmake000066400000000000000000000073531434514645000205420ustar00rootroot00000000000000# - Locate SDL2_image library (modified from Cmake's FindSDL_image.cmake) # This module defines: # SDL2_IMAGE_LIBRARIES, the name of the library to link against # SDL2_IMAGE_INCLUDE_DIRS, where to find the headers # SDL2_IMAGE_FOUND, if false, do not try to link against # SDL2_IMAGE_VERSION_STRING - human-readable string containing the version of SDL2_image # # For backward compatiblity the following variables are also set: # SDL2IMAGE_LIBRARY (same value as SDL2_IMAGE_LIBRARIES) # SDL2IMAGE_INCLUDE_DIR (same value as SDL2_IMAGE_INCLUDE_DIRS) # SDL2IMAGE_FOUND (same value as SDL2_IMAGE_FOUND) # # $SDLDIR is an environment variable that would # correspond to the ./configure --prefix=$SDLDIR # used in building SDL. # #============================================================================= # Copyright 2000-2014 Kitware, Inc. # Copyright 2000-2011 Insight Software Consortium # Copyright 2014 Justin Jacobs # All rights reserved. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= if(NOT SDL2_IMAGE_INCLUDE_DIR AND SDL2IMAGE_INCLUDE_DIR) set(SDL2_IMAGE_INCLUDE_DIR ${SDL2IMAGE_INCLUDE_DIR} CACHE PATH "directory cache entry initialized from old variable name") endif() find_path(SDL2_IMAGE_INCLUDE_DIR SDL_image.h HINTS ENV SDLIMAGEDIR ENV SDLDIR PATH_SUFFIXES include/SDL2 include ) if(NOT SDL2_IMAGE_LIBRARY AND SDL2IMAGE_LIBRARY) set(SDL2_IMAGE_LIBRARY ${SDL2IMAGE_LIBRARY} CACHE FILEPATH "file cache entry initialized from old variable name") endif() find_library(SDL2_IMAGE_LIBRARY NAMES SDL2_image HINTS ENV SDLIMAGEDIR ENV SDLDIR PATH_SUFFIXES lib ) 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() set(SDL2_IMAGE_LIBRARIES ${SDL2_IMAGE_LIBRARY}) set(SDL2_IMAGE_INCLUDE_DIRS ${SDL2_IMAGE_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_image REQUIRED_VARS SDL2_IMAGE_LIBRARIES SDL2_IMAGE_INCLUDE_DIRS VERSION_VAR SDL2_IMAGE_VERSION_STRING) # for backward compatiblity set(SDL2IMAGE_LIBRARY ${SDL2_IMAGE_LIBRARIES}) set(SDL2IMAGE_INCLUDE_DIR ${SDL2_IMAGE_INCLUDE_DIRS}) set(SDL2IMAGE_FOUND ${SDL2_IMAGE_FOUND}) mark_as_advanced(SDL2_IMAGE_LIBRARY SDL2_IMAGE_INCLUDE_DIR) flare-engine-1.14/cmake/FindSDL2_mixer.cmake000066400000000000000000000073531434514645000206040ustar00rootroot00000000000000# - Locate SDL2_mixer library (modified from Cmake's FindSDL_mixer.cmake) # This module defines: # SDL2_MIXER_LIBRARIES, the name of the library to link against # SDL2_MIXER_INCLUDE_DIRS, where to find the headers # SDL2_MIXER_FOUND, if false, do not try to link against # SDL2_MIXER_VERSION_STRING - human-readable string containing the version of SDL2_mixer # # For backward compatiblity the following variables are also set: # SDL2MIXER_LIBRARY (same value as SDL2_MIXER_LIBRARIES) # SDL2MIXER_INCLUDE_DIR (same value as SDL2_MIXER_INCLUDE_DIRS) # SDL2MIXER_FOUND (same value as SDL2_MIXER_FOUND) # # $SDLDIR is an environment variable that would # correspond to the ./configure --prefix=$SDLDIR # used in building SDL. # #============================================================================= # Copyright 2000-2014 Kitware, Inc. # Copyright 2000-2011 Insight Software Consortium # Copyright 2014 Justin Jacobs # All rights reserved. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= if(NOT SDL2_MIXER_INCLUDE_DIR AND SDL2MIXER_INCLUDE_DIR) set(SDL2_MIXER_INCLUDE_DIR ${SDL2MIXER_INCLUDE_DIR} CACHE PATH "directory cache entry initialized from old variable name") endif() find_path(SDL2_MIXER_INCLUDE_DIR SDL_mixer.h HINTS ENV SDLMIXERDIR ENV SDLDIR PATH_SUFFIXES include/SDL2 include ) if(NOT SDL2_MIXER_LIBRARY AND SDL2MIXER_LIBRARY) set(SDL2_MIXER_LIBRARY ${SDL2MIXER_LIBRARY} CACHE FILEPATH "file cache entry initialized from old variable name") endif() find_library(SDL2_MIXER_LIBRARY NAMES SDL2_mixer HINTS ENV SDLMIXERDIR ENV SDLDIR PATH_SUFFIXES lib ) 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() set(SDL2_MIXER_LIBRARIES ${SDL2_MIXER_LIBRARY}) set(SDL2_MIXER_INCLUDE_DIRS ${SDL2_MIXER_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_mixer REQUIRED_VARS SDL2_MIXER_LIBRARIES SDL2_MIXER_INCLUDE_DIRS VERSION_VAR SDL2_MIXER_VERSION_STRING) # for backward compatiblity set(SDL2MIXER_LIBRARY ${SDL2_MIXER_LIBRARIES}) set(SDL2MIXER_INCLUDE_DIR ${SDL2_MIXER_INCLUDE_DIRS}) set(SDL2MIXER_FOUND ${SDL2_MIXER_FOUND}) mark_as_advanced(SDL2_MIXER_LIBRARY SDL2_MIXER_INCLUDE_DIR) flare-engine-1.14/cmake/FindSDL2_ttf.cmake000066400000000000000000000071201434514645000202450ustar00rootroot00000000000000# - Locate SDL2_ttf library (modified from Cmake's FindSDL_ttf.cmake) # This module defines: # SDL2_TTF_LIBRARIES, the name of the library to link against # SDL2_TTF_INCLUDE_DIRS, where to find the headers # SDL2_TTF_FOUND, if false, do not try to link against # SDL2_TTF_VERSION_STRING - human-readable string containing the version of SDL2_ttf # # For backward compatiblity the following variables are also set: # SDL2TTF_LIBRARY (same value as SDL2_TTF_LIBRARIES) # SDL2TTF_INCLUDE_DIR (same value as SDL2_TTF_INCLUDE_DIRS) # SDL2TTF_FOUND (same value as SDL2_TTF_FOUND) # # $SDLDIR is an environment variable that would # correspond to the ./configure --prefix=$SDLDIR # used in building SDL. # #============================================================================= # Copyright 2000-2014 Kitware, Inc. # Copyright 2000-2011 Insight Software Consortium # Copyright 2014 Justin Jacobs # All rights reserved. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= if(NOT SDL2_TTF_INCLUDE_DIR AND SDL2TTF_INCLUDE_DIR) set(SDL2_TTF_INCLUDE_DIR ${SDL2TTF_INCLUDE_DIR} CACHE PATH "directory cache entry initialized from old variable name") endif() find_path(SDL2_TTF_INCLUDE_DIR SDL_ttf.h HINTS ENV SDLTTFDIR ENV SDLDIR PATH_SUFFIXES include/SDL2 include ) if(NOT SDL2_TTF_LIBRARY AND SDL2TTF_LIBRARY) set(SDL2_TTF_LIBRARY ${SDLTTF_LIBRARY} CACHE FILEPATH "file cache entry initialized from old variable name") endif() find_library(SDL2_TTF_LIBRARY NAMES SDL2_ttf HINTS ENV SDLTTFDIR ENV SDLDIR PATH_SUFFIXES lib ) if(SDL2_TTF_INCLUDE_DIR AND EXISTS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h") file(STRINGS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h" SDL2_TTF_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_TTF_MAJOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h" SDL2_TTF_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_TTF_MINOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_TTF_INCLUDE_DIR}/SDL_ttf.h" SDL2_TTF_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_TTF_PATCHLEVEL[ \t]+[0-9]+$") string(REGEX REPLACE "^#define[ \t]+SDL_TTF_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_TTF_VERSION_MAJOR "${SDL2_TTF_VERSION_MAJOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_TTF_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_TTF_VERSION_MINOR "${SDL2_TTF_VERSION_MINOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_TTF_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_TTF_VERSION_PATCH "${SDL2_TTF_VERSION_PATCH_LINE}") set(SDL2_TTF_VERSION_STRING ${SDL2_TTF_VERSION_MAJOR}.${SDL2_TTF_VERSION_MINOR}.${SDL2_TTF_VERSION_PATCH}) unset(SDL2_TTF_VERSION_MAJOR_LINE) unset(SDL2_TTF_VERSION_MINOR_LINE) unset(SDL2_TTF_VERSION_PATCH_LINE) unset(SDL2_TTF_VERSION_MAJOR) unset(SDL2_TTF_VERSION_MINOR) unset(SDL2_TTF_VERSION_PATCH) endif() set(SDL2_TTF_LIBRARIES ${SDL2_TTF_LIBRARY}) set(SDL2_TTF_INCLUDE_DIRS ${SDL2_TTF_INCLUDE_DIR}) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_ttf REQUIRED_VARS SDL2_TTF_LIBRARIES SDL2_TTF_INCLUDE_DIRS VERSION_VAR SDL2_TTF_VERSION_STRING) # for backward compatiblity set(SDL2TTF_LIBRARY ${SDL2_TTF_LIBRARIES}) set(SDL2TTF_INCLUDE_DIR ${SDL2_TTF_INCLUDE_DIRS}) set(SDL2TTF_FOUND ${SDL2_TTF_FOUND}) mark_as_advanced(SDL2_TTF_LIBRARY SDL2_TTF_INCLUDE_DIR) flare-engine-1.14/distribution/000077500000000000000000000000001434514645000165205ustar00rootroot00000000000000flare-engine-1.14/distribution/Flare.bmp000066400000000000000000002214361434514645000202610ustar00rootroot00000000000000BM#6(k"Ƹʶ|||nnnddd^^^___gggzzzɰ|||_hp?d*l y#Jӷtx|An{Qɨ|7wNݧQ|Kݥ* Iў ,ujw̿ c(2:::^^^ǰ|||Ό Z +4888TTTrrrǯrw{=u1k=HREEESSSiii٢ c(5>===WWWsssƮpv{KBBBaaaF%! 4IEEEsss°IJκϼǸ{{{yyyzzz}}}4  "#$%&''*H]jsssa*$#"! |%1Ȯ[[[iiiJJJmmm"""___qqqcccƤ:::;;;YYY~~~o  !#$&')*8{O&%#!  kWWWeeeEEEhhhppp|||ZZZmmm___дAAA;;;]]]3  "$%'(9|O$"!lVVVcccDDDggg{{{YYYlll]]]888CCCjjj !#%&5tT! d̽VVVcccCCCfff{{{YYYlll]]]...RRR  "$+WK PVVVcccCCCfff{{{YYYlll]]]ggg???kkk !!Rr=?@dddͲw4$2bIKMfffVVVcccCCCfff{{{YYYlll]]]巷333\\\ !:;97531/,*(&$"  A̴VVVbbbCCCggg(((DDDggg{{{YYYkkk\\\GGGuuuzzz  "$&(*,/13579;=??=;97531/-+(&$"  3㠴UUU```CCCgggBBB222NNN{{{YYYkkk\\\BBBnnn^  "$&(*,/13579;>@@><:7531/-+(&$"  1唯RRR[[[DDDhhh 444ooo{{{YYYkkk[[[:::```  "$&(*,/13579;>@@><:7531/-+(&$" >NNNQQQ|||DDDhhhddd ZZZ{{{YYYoooZZZ---JJJiii ?k "$&(*,/13579;=??=;97531/-+(&$" AܛFFFAAAcccDDDhhhMMM{{{YYYqqqXXX111FFFUUU^^^``````^^^\\\[[[[[[[[[[[[]]]aaallld %~sss;"$&(*,.02579:<==<;97531/,*(&$" 4σ===,,,DDDXXXgggpppuuu~~~DDDhhh{{{ZZZtttTTT!!!+++555>>>BBBDDDDDDCCCCCCCCCCCCCCCCCCDDDJJJVVVddd Hn!$&(*,.02468:;<<;:86420.,*(&$" o=O]jjj555'''333;;;AAAGGGQQQfffDDDhhh{{{ZZZvvvOOOPPPyyy vwx0!#%'),.024579:;;:976420.,*(&$!i!(---LLLmmmssspppDDDhhhyyy[[[|||FFFuuuDDDppp=xq!#%')+-/13568999986531/-+)'%#!]&1:===WWWtttnnndddCCCgggɻvvv\\\:::ddd???mmm y #%')+-.02456788775421/-+)'%#!_'3>>>>WWWsssjjj___CCCggg}}}yyytttpppqqq]]]---NNNuuu???lll B "$&(*,.01345677654310.,*(&$" ^&4@>>>YYY|||iii___CCCgggnnnYYYOOOKKKHHHHHHGGGIII___nnn^^^666TTTsss@@@mmm 쑖 "$&')+-/023455554320/-+*(&$"  ]*:HPPPmmmcccCCCfffŽVVV{{{kkk___qqq 444KKK```tttAAAoooj y!#%')*,./1233444321/.,+)'%#! alllwwwnnnCCCfffLLLttthhhaaa,,,***888III^^^vvv꽽BBBsss9 _ռ "$&(*+-.0112222210.-+*(&%#! ngggCCCfffFFFqqqeeecccȮ\\\yyy@@@hhhGGGyyyN "$%')*,-./0111100.-,+)'&$"  ekkkCCCfffBBBnnnbbbfffLLLnnnBBBlllMMMBw!#%&()+,-.//00//.-,+*(&%#!  \sssCCCfff>>>hhh___jjjEEEjjj???hhhVVVS "$%'(*+,-......-,+*('&$"  +W{{{CCCggg666]]]VVVpppDDDkkk888^^^aaa1!#$&')*+,,----,,+*)'&%#!  8RlQQQ[[[CCCggg,,,LLLiiiRRRyyyDDDmmm...MMMhhhooo "#%&'))*++,,++**)(&%$"  HQZUUUaaaCCCggg666LLLUUUEEEppp!!!888LLL!"$%&'()******)('&%$#!  UUUUUUbbbDDDhhh"""...fffFFFsss"""///qqqu !#$%&'(())))(('&%$#"   s^^^UUUaaaDDDhhhUUU}}}HHHuuuTTT˄M "#$%&&''''''&&%$#"  jiiiSSS]]]DDDhhhKKKxxx- "#$$%&&&&&&%$$#"!KT[|h& #guuuOOOTTTDDDhhh葑RRR~~~М-uʻh$#$$%%%%$$#""!```눾 ;mHHHEEEjjjDDDhhh񊊊aaa쥥c¹,"######""! !unnn ]~???222LLLdddsss{{{DDDhhh÷ooozzz촴)≜(""""""!! -f}}}i666...===GGGLLLOOOSSS]]]oooDDDhhh✜ܿ-ד !! ?\u)kkknnnDDDhhh)ߌfX\`%nnn```DDDhhhy(iiihhhYYY|||DDDhhh :穩|&lyyygggWWW{{{DDDiii :ahhhYYY|||GGGkkk[W_ennn```NNNqqqMlll|||nnn___~~~'mzzzBDb|khij#q Dj rvy~ 2oq mu}[ ,t?  t~$ Bv |ގkHz|&{ΝS|单- } w} p ^flare-engine-1.14/distribution/Flare.ico000066400000000000000000003470261434514645000202610ustar00rootroot00000000000000 Hf (I@@ (B6Q00 %^   hPNG  IHDR\rfHoIDATxy$EuuW1p  "ùp8^z%2" s}wUeTUwVVfdVewWWu>̌+#<>|q i.t=|BWAE\V.>>^B7BO%@]< *t|^Pb:6\{xWz ĠK~c :-/||+t|>Pb( [@G1 ]S pMm{_/t=}PdXZS|@IBjZ_o# ]WO%H"YS(|@ B|:wBJ As/v_,t}<|(UYԐ|9JpȄ_= XP}$WUs5 >>(>cz5hҾ1\?J? Wo>҆O  uw\W >>2t^R>|RO%2^B?O%OlbDvV\Zw\ L>>: 80ėnPG1u !%Ww`aℐqV15 ġi0| \U ].8⣈@#Km?m)U I]kh*J`*Mψ!h +\q6ˎ7|?Q&4xRAM9jdCG `@UݣI-!o_Qo\s|#?0 ={ UaxR7D͉~N'9O*mwdM(&kh8Z&)X| `*]|Bq2:zMśH58E` o_G>S>32$8@c ]Џ|#7õA!q7GA\5)4ԯ",8mY\+)0W5UtߨDmUZ\6ߺuh'9b [ifRcT9ܺ< >n#lQ` 4Hap ဿlOsv,aGX8DÂUnV 9>%'?@2Ql 3).X5T}d'&1ݵ(DEXh(}d'9*~hrA8qV>|Z6 ;U@iɓkNP0ͅn `a ; KHb:diX', X{/n `!5[fh\fd@DBl Hɗ5 `a*rhP`idZ > `A:L5R!K „Ol"n >=hP:7fؒVT>\PSB7NUИNX, tx5 s>5hj`x<{ dEx*0,oq\RxkOypj{Tc1gY~cJ{ !85Hɿ#O@瓅BH$8͟gA0E[BNŻm2@ظrg߳B%:]*K1 @&"TO>XぐF:ui-5u$(8Y 85NÅO9BoO(t]<_sk!XWd,H&|-P GHJFT `$Ex:,%Ѱ`aOpW>䀔Xz+Vas.1A PI@uvV2{ .s>{/`wѽ Y:K K &HmU2@hd)NGD9kD`iqwFsOd,^?Rz`)kRB)~ۂcwa| X @9O7 @8Z0$(}3`Kl\C0͎=˩+tr4X}4I@7PWNݴ}|pXr⏹c,trvB@Yh@$~Z Fs >t‹Xf*DS^6i!8De~-߹c ]_+`Cw&&8ЕB@N ™X8G tn"y󩚩v !nJŅMD T?,{ U7M\ڐVP_$PZMѲم)2Ossrw.)3hTY:"[, nh&&όu2\XfjC5[p(t!U}*4F#Jtn2kC5N6ڰ p,f@0LK./!f4߾3ɷ̌`n$;Ǡ!H>ZM .^#'o&|ubVҴ+,P3n50IBH1}…B.^PO K)]Yze×w^4  !{59 M:g 539?ZYLZPԱqcMq `B Ob>8uN}Ue"l-ɴc&QH:W9nR2,2)1on%e&9e=X9JmڭDf @q<AJIB|©R^:jn\33lS?:8ԢWBB0 Zeԍ|ݴ W+7aT{'4*㰵.S4~I=4U}ϯJtq6RrI@< t5|P@ ݨ.p27P='7]UQ DUHH{oTjGG/SOivMpW} KTeWrPG/m\W.Xn祴\^G$p.(n4ۙaTDy$@4'mȻkR @nUxB"`_!$|Bxv]nVJk*gj_X! YdP_D,@{yKy2O*%ÕeNYQ4uB_̵^</ntJkwt;ŹՎ@݃໗aA~&nmˉ !N]QM((ږ5%4Wm`zMp_2͖?DyyWN+9@P`e>q #\LOsBSk:Ɨ:ȩGPΩY4^~s*Bx g7vPm-~Lg(4$υRڡ݄ ظ#uxQLD`i|P`/UiG\-8:uhn|{ph.ԇ/I)لK'VcC'ׂ0빂 (5 H/VwB t jSmA m$Jty0 o Q=$(#j9* GAj|J \;j¬:@}]aAv@ iBB 6^_#BCUNmU_!8-HETaUjoMՓ1tЂ kf&,HEiP>>$2`9'p05׿a@R&&7C\K{ Kfoa޺g1k6\hH88|yI`09hAs'2,B5UCI"4U *#Ԅ wu0>ʩ,ϙvrc̀bQi |{yC|q&o ^N`gؼc(qj-@F93JODVQKk6Yqg-Kcz.?,08?'UA {6UY1?B]F戆@EX;geS+5PWηkF"Ќ ߽ȞdA?uMG!M e45&'@". vY=ψ\lԉLGZ;q@G2DO~@ш0 BTi{4J1vhX͐Gf p݌ `0M?KgP J7I3 Y5 pn 3v:`s0Ts^iBWhms(g[;RÊEeT}ce;e)ő |GO=rw}O$.z\>_w FS}zꯍp/ؕc'VdҔ$ݲ 3QHKF9U,*(/ P PU*<@yrf 늰lR.=߿M2s͏}R";|hIkݓ&;K0GWV*N<=Dp"ll}l @K}+,<hIu5fHhf@P`iEa!d >˸oh5WE N K7qnH`3'1➨q%.!8qPX%,'U&"(ە$6a)6Zݵm&` TcĶ /Jxt!y![Gd*sPk}9XJPKJqg&V=]ږ*YSE&%*z.!85M΋Y޳?/X#{[W{(KH"Jz^ŕЍc'I%#Jh o 3t:^wb^UIlzԬs ^E^+uRߕ`q9yW98D1R:Z@CY]|kz+EU|eoe8vaNGC;;@F}܌C=ҘffYi_Me8KX~=:"^{XsR@% N,P^{0!؝out"Sx~ˈ:,զbJEM!xHn~x2~? HؕZ( vGsZs@v!,cLy\2,DP%%s"lx ޞR-4VkiKVm5cv:]`ؚVV^5&8} }]4r#@ B6J놂U'/vФmMavϘN3G4sO^ˉVvE& 0]QHGReU`%;7V&ߵ`v[k&3B`d~[ږoYPDYHNL+Pp'9(@@ÃWT厌XU;Fl]X]yɨw6?o" hՅB0*'Cyl[8۩V38*m ý 8{}g_doọSuiQ`)Ui q4J/n6"/-zh$ YpwbVó $xl~u1/ ڊ],_=Rf fg H^h,n 3*&HGC-w.y8KI62ueHn:;:wPߵgbӳc.Cϐ?;J-Mśru:kQR9i\;^~P%HYzvL Vłˈ24NQew'qs:sy (M #eXK*L{:GyN^|Ǵ,{xx9s񘗂 %g{=R<ҬࣗqyDT"k.7jRyƴFr+nERE@ s׷?͹lFp;(^$D L"~+HUO{xs[Cq@ O+f$HAG'{0ed~HW7ᦋ7?YiMJ-V]#t f1oJm{-h=u]ұ{-۷3$#swy+(I&-e],*|`*auU89P /ߗ"!r,ztK!7yeϖ}zu ' Tj(IlϽaM^=x^ R=oHU *G… D`$25i[fWH蒝;ٲQO:Qϝ{RP56p g, ,lnżə#:ث k-Eެ^kQF ?өED<}lX±u|w:Jn0^Ɖ @k94L._U Dӹ݆Yw7AB&= a(*)H5S8Pr,Ut]e .ea溰s ݸ vNC[ީT j,]Vg,f"ZIV ^ɕZ[P@r*Y8|iVߵ>RvC|F6l;|>`ZN?m!+oV;zWGޒoA"ǯfC^1*DU4*ݯ)J8l(;5gCW#[ LIV]{^M,XPiga D˃yohw_?]1x@J֨ނ&jEB<7lJڙ{qf-p={hg_-N幈t pnM̯Umqx=h0 Ed-3QR P<06FМ>1&B]|ȱ[Ϲ5 ] vϓ[?ykA{k+m|OW_kU(Hrc9ek#q7)vl\C@\2Z߿0ʬ]!7︫p'H@~yjAYuL#VGMe(_g!'bWqTp$6(hso/ 8*&`K &B= Bgeon(ģxaxVJJaØBI\5jsT ^k?PfA ryhp‘u\QG}un>#x?U\q yN(zxDwҴԹZ <U!^(Z¬\^kh͟#䞚"'P/Uu-5 5YjЏ>aلڪRêi e$Ԗ'fguEOVUT,ǔ6!2ZRM8\Mi83 rAUxx5BI ȇ 4 [?4ѢCQŏ]AsTi!kMUy" S?W8ji'ZIsMȋy Alu^4ښ'T] 3 M,/FG`I,3_fuyD9J /F-p/@YțT!ԈuTP]ihɄӜ}"]$W*sa;uYKOܹAeQ jJs}ŋ҆XTgEK/#3RQi2:F9hq>Q0$<^˔ߢ,K:`3=锺dF'7TT;p12#91@Z# `f;@zڶrj©]c(a*((Ɂlv$%{`4̯cQ󩮏fܰG!Ѱa6'4#2ӈ%)սuD#o5" yu3kdBw~>垈YEÁױ6]8AB.Mഏ(LT:/Ģ cbX#3% UdSA\U|`.|Osm6;e 7;] q:wйƶZJMCZE` ڑ0!DrUu#ԍ8bcU)5=,&>c>{r?dӥdlѡi?ԷTdE+фuڠ6^ۥ"һ1v:A靖04!?yNIâp)R 917;݌ 'ӽ*B]SzU$ Ff؅~ÑqɶhhIrѢ@QRF5'::oSD\lm#v=?@mcGCyBWU0;fKӓϗ|nhpS5V4UMq$:;_dk{ $|X|=0l>ʒ46WO5ǝe l7ƾXf) @JS[.%({cY*[ PYyki_PqL;cG0ƽ>+BrӁa(O gvGh 0&8L;:g.voFw3n{:\ˑ 'rHYhkF @ٺk=R4UEa*##π3Ϝwt$έUkԌ_ |  ҾYue sg•Q(FO^& tAz|##1vlfϮ>Ru=-oBoSx/BT`~u L'-@#lNJx?"G1d0N?(r{&m?;uoW/$_ pGOe|xJN(>Uf_JIWv0U΍oG ~3"]E]EUKS[wa[}380ζ-=?{)&MrlNy iobAk@lаIPx2!ᬻgTɫX .,(8-L=ou߁qo@אWrAu&.~])$^bDrCɭG%qu8۶Y;C+C_OYGk Q% )Q)a]P (*,4ް+ЙK;(BZo,h$?"eݦ1?EEO]!wE?Ύtu_vHMJs੫h0$uS]]c~'(F$M 8M1;YׯkhQ SPd\ |̻E1Hn/@r]^Sr @˟>Ǡ'%C0>Ȣz $_^(!S hg\gg~z{=̽,}%lZJ?35=:]B?lDQ/e'0.!¦S4ڇr5+BpK |[c䳧}g?=DMP=+Ⱌ;w;3lXO*t:nX (:5yv Ty݌ྌqvl>@ޔ|_]K[uxh(}cz@ <(>o﷋_X!w4yilKo3l8XL^Wװ00hoPP ?̎AFF<+C?p\Xtk7ThhM/`ϻue\[7cks]n pǪMx3% /5۫ho$Y_J:F1ȰG"p I ǘڪhoJ}LBƵtIW(;Y n<ιU`SW ѩ C4{!Dp["c[x<R@r)w10_^aRo. WZE Aj=2>eaD yNܰN6,W_萦 @$~~h[{طqe _t-˵)|XIeH jimL s+HcǞS,Bہ+4kqL5`yȀ`l,ήm=EOx_07S}-H5TM 615 %qw#^:7)YEI^Ǖhp0+`ss^p%X[siߢ3H><;eeՀͫh KW Z9o(Ǝy' ;m>D |Q4'bAv"7ߕ<-X$mK20$$ߌvgd߁rhhW99 d@=^5O]ۊ_'7Uj:/8~fPUeYNa2ly0w ;˭z&%NRpEeKiW-[W VIcp$ήqn !$>.~mf 6cYjPg8)HKuC//w"< OMnϛ xP&\8KAIohaz{{㹫т>+ࣧa?PlF.>eAX!h n`( C$:{vбvyKo`;-I\ t^>x̅Kz]| ;M(A  `8&IPMTI2)OI!840+:#Δ7X^ptY鰉2aRh_@:ckc3Cյ,\@mC4Os?*y awm7K(qJiDQk*BT[-0:4S0ǵURcki_\O}SEꦩ? C?7_:Ft6~pq4Ŋ&u)*Q! L5K>fĹ)~k;u^vmbldj)'wvP;^W]83%7PK%tvG*dƼ{ܪTGp$'t^2ȸӒךּ˅_TPN4V$_`[ui];MimYv嘏նeuR 45#&:,飋/;{ycψe~Tg*V@Ru8,n ZD{~! ::ؿwڀRr 8P$("[?g랑LW5O];*e$<^T>^,Px7 VW ʩߠg:x{9Fp!moMh ߁%}1:{&L!IwhbA.%$ TA "9Pf`ȧ0FG=@a<- .B|iZ ،qibA8@(EBH 8z8;x,gȝ'n\AoL]$/eUae%_)HJt{{ڏKAn]}+ϭc9=4ԒD% X\@z7BwފӉ&__S ACGFt% nΣSXL`| 5Zin( ƁQzǽ!8eَ'>LngNiC,hLme BHcQo&)ᦱqX}7 ,&kUTAsRzCs q^.l Vv7}B0<c]^m[Fbloog|Gr`IBlœ ߾wHc89,i\c5 Xf^9`8LY qV0M1^XGc QǀiEt`;!):6 XӱAo? Nhgc>c~9|!m}z#,͏I^f~jt y{hRpÉ|Tߌ?v,G|1{?& <+З|S9GO8O'IXdiKBؘ@`QN)yt̿`K6TKD[6:1' u$iX7+4j+'Țg Ĕ^wvnҕTq&g m 7 ^s&1g ^8\$WqD0 XZFMejn!?4 <;_A𹕭<4WL48 ,NKSaN$ǨOJ8m0e/PFb7BG+V,ǜ'HzwnTXRNY$k;b>R}:O)t%Q]Ahhaqx4QNƊgF ظ:.%_$ )X=Gup $ܒU#W`ԌL*C7Q^$ FF?<DGel871= l䚷:D{?@F06g0Ftn?yh Ck̫P5Ξ}\O??tI'v r=ڋ{7 %Une#D$DrQнD xB\wX`-#V SSBh6BxLgo0:G.: k8:G >䀍k,mCRV@T QU"ILlF oq[[Qh~s=HX"D,Hf%=x!qorw#vNsI>0 xao +T> .o𳹶u1ZJc$I܆O3^ϒ@x|ۿ ܃΃j:> i! `4j?}>0 :JJGK(`QōJ70'Ճ|,k) Xh,ҡ]HQ/ui.$]R>|Ç>|Ç>|Ç>|S `:$IENDB`( aagfghhhhhghhhjbd\ghhjg hg&h=hOgWgXhWhFhrhhgggfhifhhLh~h̩hhhhhhhhhh̋hBhjhhbgggh~hhYh̝hhhhhhhhhhhhhhhh̾hvi0i jhhfeh`h+h~hhhhhhhhhhgghhhhhhhhhh̯h]hhhlgoh&h̃hhhhhhhgiknrvwohhhhhhhhhhjhhijghgghhhhhhhjpz ykhhhhhhhhmhahhggag3g˭ggggggmy  ykggggggggigfhii]jgggXggggggn ~xkfgggggggegjhihdphegʀgggggl|wjfgggggggagfgeffhgggʘgggght wjgggggggʼg^geghhdbfffg`fcfffɢggffj|  uifffgfggɹgYhggjhdceiffEf&gfffncegfȝffffl  shefffffffUffhhhfffiefcffffțfQg[gfcdfff fȎffffn  thefffffffTgdfcdedffffgfffffffffȆf=fjfefefhfsffffo vieffffffȽf[fgeffeeefffkfffffffffffffȼfqfekfiffeLefeem wieeeffeeǿe_edeffddedfeneeffeeeiheeeeffeeǰeeeade%eeeej  xideeeeeeebedfffeeeme eseeeeeeej zxlfdeeeeeǜeeegfe eƛeeeg}  zjdeeeeeeeffdeddefeee"eweeeeeeek | sieeeeeƄdeejaeXeeeev  }jdeeeeeeejefeffete%e{eeeeeeel ~ oeeeeekgdcdeeeeeen ! ~kddeeeeeene^d'eŀeeeeedem ldeeeeUhcccQdxdddg  !"##kddddddddydŃdddddcdm iddddc@fbdbdd$ddddt  !"#$$%%lddddddddddddn"" ~gddddd.dffbqdxdddi  !"#$$%&'('!hddddddddk %$#"!  zeddddĹdccUfddddddv  !"#$%&'()(!hddddddddk &%$#"!  veddddģcddkccVcdch  !"#$%&'(( kccdccdccddcdm$$#"  rdcdccÌc ccdecÞcccr !"#$%&''jbbccccccvcÄcccccbcm##!  {eccccctcccdccce|  !"#%&&jbcccccccmbbjc'c~cccccbcl!! qfccccccc<baccLccci !"#%$~ibcccccccibgbcdcic$cycccccbck qeccccccc¹cXcb`db{ccbq  !#"|hbbccccccedccabccckb!cvcccccbcj~qebcccccc³cSbecabbbbbbw !  ygabbbbbbbdbccccbac_cbobbbbbbbh {qdbbbbbbbbQbcc_ababbbd|  xfabbbbbbb]bebcccdcabbnbbbbbbbh yrdabbbbbbbLb bcdcbbbbbe  vfabbbbbbbZdab^`ccbbabjbbbbbbbcabbbbbbbHb bbdcbb bbbf  teabbbbbbbUa`cdcbbbdbbbbbbbbbbbbbbbCb bbcba`*aaag  td`aaaaaaaVa^a^`bcb^abeaaaaaaaaaaaaFb aacba`)aaag  ve`aaaaaaa[abaaaaaa]bakaaaaaaab`aaaaaaaJa aa_`a`&aaaf !  xf`aaaaaaa^aababaaabaaoaaaaaaag zrc`aaaaaaaMaa`Y^aaaaae  "$#{g`aaaaaaaeb_`aaaa`]baqaaaaaaah |qd`aaaaaaaR`b_]_aaa``c !"$%''}g```aaa``h`]acb`a`X`!`u``aa```h~! rc```aa```X_a_```````b{ !#$%'(**"g__``````kaZ`^_`p`%`z`````_`i$$" pc_```````Y_``ca`a````u  !#$&')*,--%h__``````q`a\`&`}`````_`j!''%$"! qc_```````^`ScXa`r``_o  !#$&')*,-/11(i__`````_x``````_`l%+*('%$"! pc````````-`_``G``_h  "#%&()+,./0244+j__````````_`m(.-,*('%$"! oc``````>`_____b  "#%&()+,./124577.k_^____^` n+10/-,*)'&$#! l`___________u  "#%&()+,./134578::1l_^^` o/5420/-,*)'&$#!  l______b^_l__^k  x!"$%&(*+-.0134679:<=>4 m q 2875320/-,*)'&$#!  ob_____a`a__1___b ob!"#%'(*+-.0134679:<=?@?56;:875321/.,+)(&$#!  ob_______l]g_^ _^^^u c^m!"#%'(*+-.0134689;<=?@@?=;:875420/.,+)'&%#!  pb^^^_^^__^__da]^j^^]i  z^^_!"#%'(*+-.0134679:<=?@@>=;:875420/.,*)(&%#! oa]^^^^^^^Y]a___^^*^^^a j]^]g "#%'(*+-.0134679:<=>??>=;:875321/.,+)'&$#! oa]^^^^^^]S]^^Z]^^^^^^ta^^^^~!"$%&(*+-.0134679:;=>>>=<;9865320/-,*)'&$#! oa]^^^^^^^O^^^__c]^_^^]h t]^^^]h!"#%&()+,./124578:;<====<:9865320/-,*)'&$#"p`]^^^^^^^O^ ^^^^`]^$]]]` e]]]]]^!"#%&()+,./1245689:;<<<<;:8764310/-,*)'&$ o_\]]]]]]^H_ ^^a_]]]]]]z {]]]]]]\o"#%&()+,./0235679:;;<;;:98754310.-+*('$n_\]]]]]]]B] ^^c`Y_]|]]\ti\]]]]]]b"#$&')*,-/01346789::;::98765321/.-+*('$n_\]]]]]]]C^ [\\\[]]i]]\q ~_]]]^6]]]]"#$&')*+-.0124567899:9988764320/.,+)(&%$n_\]]]]]]]J\ \\\\]\\q\\[rj[\\\a\n\\[ t #$%'(*+-./02345678899887654310/-,*)(&%#"!o_[\\\\\\\Q\\]X\\\\\\[u ^\\\\=\\<\\[h"$%&()+,-/0124566778877654321/.-+*)'&$#! n_[\\\\\\\R]][Z[]^\\\]uj[\\\[\]\\\c"#%&')*,-.0123456667766543210/.,+*('%$#! n_[\\\\\\\V[][Z\\\\\^h]\\\\C\\\\\\a"#$&'(*+,./01234555665544321/.-,+)(&%$"!m_[\\\\\\\Z[Y[[#\[[\\[\\\]\[[[\[`!"$%'()*,-./0123445454433210/.-+*)'&%#"  m_[[[\\[[hZ[[\L[[[[[[[[[+[][[[[[a!"#%&()*+,./011233444333210/.-,+*('%$#!  l^[[[[[e[[[[[[[[[[[[\`Z[[[[[Zc!"#$&'(*+,-./0112233322110/.-,+*)(&%$"!  |a[[[\L[[[[[[[[[[[[[Z[[6[[Zh !#$%&()*+,-./00112221110//.-,+)('&$#"! d[[[[3[[[Z6[[[[[[[\ZY][[W[[Z o !"#%&'()*,,-./000100100//.-,+*)('%$#!  `[[[[[[XZyZZZZZZ[%ZM`XZzZZY w !#$%&()*+,,-..///000//..-,+*)('&%#"!  ~]ZZZZZY[[ZZZZZZZ4[Z][\ZZZZ !"#%&'()*+,,-...////..--,+**)'&%$#"  y\ZZZZ ZWZZOZZZZZZ>X[YZZZZZ] "#$%&'()*+,,---...----,+**)('&%#"!  uZZZZ`\@YZZZZZZYF`X[Z[*ZZZb !"#$&''()*++,,-----,,++**)('&%$#"  oYZZZ|Xb_ZY8YZYZZG]XXXYZRZZYj "#$%&'(()**+++,,,,+++**)('&%$#"!  jYYZZaZ_XTYYYYYCZYYWZY|YYX t !"#$%&'(()****++++***))('&%%##!  dXYYYFYYYY$YYYY@ZXYYYYYYZ !"#$%&''()))*****)))(('&%%$#"!  `YYYX+YZXYqYYX;]XYYYYYY] !"#$$%&''((())))))((''&%%$#"!  u z ]YYYYYYXXXYY6ZWWXX2XYXa !$%&&'''((((('''&&%%$#"! gWXZ\`i u  z[XXYYYZWXYXX3XWXVXXYXXWj re`i%%&&&''''''&&&%$$#"! _XXXXXWX\i tYXXXXXXW XrX2XXXUZX~XXW shZWWWWXg#%%%&&&&&&%%$$$#"!! [XXXXXXXXW^w oWXXXZWWW XXXXXXXXXY |mZWXXXXXXW\|!%$%%%%%%$$$#""!! }XXXXXXXXXXX[vjWXXXkXwY\XXXXXXXX[}_WXXXXXXXXXWYv!$$$$$$$###""!  rWXXX{XX/XeXXXXW]veWXXXPX@UUUUWWWWW] qYWWWWWWW@X,WWWWWXw!######"""!! fVWWWKWXYW WgWWWW][WWWX4WWW+WWV`jWWWWWWW9VXXUWGWWWWY"""""""!! ]WWWV$WWRWRWdWWWWWWWWWWWW:WWVdiWWWWWWgU WWXWWUW2WWWV^!!!!! YWWWWWWWVWWWWWWWWWWWWFWWVenWWWWWW:WV]UXXWW9WWWVj  xVWWWVWZWVWWWWWWWX XVVIVWV[YVWWWW#WWWWXSWZWVVWlVVWWgW_TVWCWVWVWW]WVVIVVVVVVVVV"WVWXWWVVVUc`UVVV;VYWVUVVVVVVtWCVVCVVVVVVVVRWSU[WV*VVVW}ZVVVVVQUV,VVVVVYVPVV8VVVVVVVVVUMVVVVVUc zVVVVVV`VUVVVVV>VVV*VVVVVVVdUYVVW'VVVXmUVVVvUXPVW0VVVV$VUUUUUUUUV$UVUVUUUT l`UUUUEUWVXUUUUUUUUUUUUUST^UT@UUU\YUUUUUWUU7UUV UTTUUUUUUhVIIUUUUUV~ wUUUUUUUUVUUVVUVUUUUUT2UPTWUUUT niTUUUrTYiVUBUjSXUUfUTTUUUUUUEUTT`\TTTU<UWQUUUUWMTT9TTTTVUTTTTTY |VTTTSSQQPQTTTTTTnUNTT TTTV }nSTTT[UTTTTTTBTTRZTTTS u _STTTTTATSTyTTTTTSuTTRp |VSSTTT]TSSLSSS SMSSnSSRn kRSSSUTSR#SSXRSSnSSRm [SSSSSSSSSS`S_OSSvSSRo rSSSSSSUSRSwS5S@NTSSSRq _RSSS{QSLRS/SSRQRRRRs sSRRRR-RSUUVVVRRRRRSv ^QRRRRRTTTTRRRRRU{ oSRRRR2RQRRRRRV ~YRRRRPQURR#RQQX fQQQRR(RUQQ;QQP\sSQQQQp\O@VQQYQQPb}ZPQQQQQP[OQxQQPhcQQQQPAQQQRQQQQnmRQQQQ}PQQQQ PPPRstUPPPPPPQPPPPPTyxYPPPPP4QBPP4PPPX}{]PPPPPZIROPPPXPPO^}aPPPPPRQQPOO{OONe|cPOOOORQQPPOOOPhdPOOOOOOMOPOOOPYQOOOOO+PRUOO)OOOOOOOOOO4ONNMNNPNOONNNNOO9NOOLNN{NNNNNNNN=OOONNNNNNNNNN=OMNNNNNNNNNN6QMNPNN8NNNNNN)NLLlMMdMMMMNNONLGMMMMkM NNOMMMMMJSMO@MM!MM/JNNNNNNON?`???`???`????????0p?|???????????(@ @aahhhhhhhhhjihehhhh hh/h7h2hghh\ggjhhhhIh̋h̾hghhhh̀h7h ghhhghhhhhhimqmhhhhhyh hkgglgFgghp } }mghh˾h]hihjffg guggo  ogggʻgXghh`idjfffahfff gɎfiy ~ngfgɶgTgggkfffffff%fffeeeefffȋfjsfeffȝgffefe^eeffYfȼffȯfaf!geeffemfnei nfefǸeUeeefeeeeeee\fǿefifeeeLjeeecee>ef| oedeǻeXfeeddeeaedeo  rgeeLjedddeeeƹet pedeƿe\edeeedeq heeo^dd`ddbdk !# rfddddfs}fddWccbhddddy  #$'( tcddd v%" xeddAd`d`ccHcj  "$&#qeccccer " mccc1cdccc‹cs !#!ocbc¿c[bccdcbdp pecccYcdbb bc|  ncbccXccccccc`cbcnqdbbcc&dbSbbbe lbabbTbccecbXccbb\bablpdabb|b#eccbaa"af  ta`aabaabbb^aabba``abbEbbbbaa#af la`aaTa``_`aj``aa]a`alpc`aa}a$^`_`Uaa`e  #!na`a`Y``````````anqc````'c`aa```b  #')' o`_``]```d`_ao"# pb_```*aa````y !$'*-0- q`_``_a r()&#  nc```#`___m^o !$'*-0373" s`a t .0-)&#  r`___`__4_e  x!$'*-037:=9&&4630-*'$  mb______ ^_|a z!$'+.147:=@@=9630-*'$ na^^__&^`^^^q]n x]c"$'*-0369<>><9630-*'$!o`]^^^&g_a__^^.]c h]] y!$'*-0369;=<;852/,)&!p`]]^}^$]^^^U]] ]^ ^]\f$'*-0258:;;9742/,)%a[]]]D]]]]\\\] |l\]z\^$&),/1468998631.+(% o_\\\~\%[\[\@\\ \^p_\\\[#&(+.0356776420-*(%" n^[\\\'\\\\\\]_\\u\\p[{"%(*-/13455431/,*'$! l^[\[l\\Z[[?[[[[[[[zZ|!$'),.0133321/-+)&#! g[[]\Z[[{[[[[6[[[[!#%(*,.01110/.,*'%"  sZ[[[[ZZZZZZPZ[[ Z\ "$')+,.////-,*(&$! mYZkZ[XZZJZZZ^Z[ZZZ`!#%')+,----,*)'%"  hYZOZ[`YXYYZ`WZZYY?Xg"$&()*++++*)'%#! cYY6YXYY5YY]YY[YYiX q!#%&'())))('%$"  ^YY YXVXpXX\YYXcXX {%'''''&%$" |Y\al  ~[XYYXX X,VXXXX XZ y`XW_#%%%%$#"! pWXWW[uxYXXXWWXXXWWW]fWWXXWY{"$##"! eWXKX/XW[cXXSXWWXXWW(V` }_VWWGVVW[WY""!! \WWWTWzWWWWwWXWW1V\^VWWWWWWWcV_ XWWWWW WWWWYWXVV0VVVVVWXUWVVVU u tUVVVWVV=VVV>VVV'VVVVSVWXWVV7U]gUVQVXUVWVVV'VUUUUUUUUVUUUU{\UU&U@UUUBUUUUU UUUMSXUU[Th VUU UZUU UeU UTVTTTcTVSTT'T\oSTTVUUU UUUTTkTT8TRTT TW`STDTWUUUUUTSS?SSSSSST ~ ~VSTTSSSSSSSST } jRSRSSS S1SSRRRT ~ XRS7SSOSSSSRRRV  jQRRR[SSSSRRQX zVQR:RSQQ2P[_PQPQOOQQNP`iQQQQQPPPpOeqSPPIPPUPQPPluUOPvSQQOO OQgWOOQ PQOOOPROOOPPLNN>NNNNOOOOKNNjNNNNNONMLMNNNNNRMMMMnMNNOMMM<NMMQx??` ???(0` $aagihhhhlhhhiighhhh h3hcgˇg˗ȟhNhhjgggghh`g˻hkomhhhhJfhfffg;gʺhr  }ngh˼hZhhhedfffffffg\fm  phgɸgTg hhhhfffffgffcfffdf]fq qfefwh fggfdfeeffVeǮeǔfFfefeeee@ep  yieeƅe)YeeeeeeYdƼfnlfee<e_cdeeek  |jddŊe,fee]dfq lde+eecdeduf} !kddŞdf t idļddcbddco !$'$edco#! gcæcccbccTd|  #"recücfcÒcj iccoccbdbg pcbcVccac/bbh {qdccucbcbaak mbabOb ccdcc`b+aagmdabmbccbcaa`l  ye``a$_```^``a aX`beaaa=b```^aa`k  |f_``+^````Z_bo ~ha``D`````^_g "&)#g__`6`^^a r"" zg```Aa`d__kb"&+/2*h^a u**&" ta_``Z__5_w#'+/38:1%33.*&" m`_^___ ]jrq#'+/37;??;72.*&"n`^^w__\^]sa d_#'+/37:==:62.*& p`]^r^`__k^]]5] { {]\ t"&*.269;;851-*#z_\]^ ]^]]\\$\sg\\e&)-14788640-)$}e\[\B]\\\[\\7\e]\N[[`%(,/256642/+($! yd\\\?[\Z[[a[[[\Z]_$'+.023320-*'#t^[\\ZZ[[[[%[Zzb#&),.0110.+(%" cZ][YZZ%ZZZ9Z\Yh!$'*,-..-+)'#  `YZZZZYxZZ@ZYYX q"%()+,,+)'%" \YcYdUYYYY@ZYYY+X { !%'())('%#  zZYGY_UXX.X4YYYXXOZ whw&&&&$# cW[e}tXX.XUVXYXXWXWr]~_WVVe$$#" \WWcW^cWXXUUXXXWWV_pYVW2WW?Vh!!WW=WW-WWWW WVWVXXVWWWWWNV{ sUWWWV^VVWWVVVVVV&VWWVVU`fUVVVVVVVUUUUtUUVVWVUUFU[TUUUUU\UhUUUUSUUcUXXUUSl }UUGUVWUUUUYTT-TT7TMTTSalSTTUUUUSSSSSWSR\ \RVTYYYYRSSHSSQSR\  uSSLSTRSSSFRRQ^ ^QRRUSSSSTQQPa nRRSRSQQPfyVPQQQPP'Pl~\PQ(QQOPOEQmaOPOPNO>OOlQYPOlUPQNaNNNOxOOPNN NNNtMNOMM)MNZONNMM(M;NNN??????? 08axaxxx?( @ aahhhhfef.h#hhhhgfgg fViͨmmhh̘h"hhfffffgff-h˱r  |ng˺hWh hfffffffcceee4i{ qgf\ggheed dRdpd.feededg{sfȳbQc ccbVgȺrwleƂeedd\dćs  vgǹdzgǽ vocqedccb$h#$ici  gcYdc_ca[o  teĸbRcbWeĻ sqdcVdc_ba}t pb_\`bcb` _abjc`yaacb_a`t  rb]Q] ^]]Tc rrc_e_^``]`^do &( vb\wc u" ob_J``f_]5g  &,24&#.,% ~e^l_`^\ ` m&,28>=81+%oa^g__`V]\ztu]&+17;;61+ s_\y\^__\\[dhdY t$*04774/*"o_Ze\\\\[[]\WG o"(-1441-(" i[[[ZZZZZ[W\ s!&+.00.*& {[Y[WYYJZZ%ZX|$(*,,*'#vY[ZXXY`Y(YUZ&(('$  t z oWxYTXXXYXT^ u^^#$# VWdcVZXYXXXUWU%\dVT T(Y! rTmPVWW?WVV%VVUWVSDdfSAVVVV+VUUUUiUVURW[RUWUOUUTTTT8TWWTRy r yURUVUUUSSSYSSUSP`jfQaSUUUUUTSSSSQRPlk }WPSUDSSSSSRPnbPoQPQMQqlQOQROMScTN-PQNN&OPNAOMPONNMNNAONONMMCM3NNN??(  aafgf^ mTnғhoh ggf edecsڠyngNfV e8bjcdNoՓ rЂg_ |ܹyf/jcda,z tѳgƏ gɃTcdcHoY0? ew vob?^`a_3}"' n^/`^P r v/97. sd[] _[Ra`,44+ {e|Q[UZZ$Z|]T'--&oI ZUYYSdl & ~qfĬ-fYUYZ_Z^K' rɵlšRDWw\WUWXTT^S5Z\W_kULXVSSS UU? |vQ,UVVVTTTXXTo` (PZZSSPWS{XNORMMN]L#PRNflare-engine-1.14/distribution/emscripten/000077500000000000000000000000001434514645000206715ustar00rootroot00000000000000flare-engine-1.14/distribution/emscripten/make_emscripten.sh000077500000000000000000000016651434514645000244060ustar00rootroot00000000000000#!/bin/bash cd "`dirname "$0"`" cd ../../ rm -rf emscripten/* mkdir -p emscripten FLARE_GAME_PATH="../flare-game" if [ ! -d "$FLARE_GAME_PATH" ]; then FLARE_GAME_PATH="" fi if [ "$1" ]; then FLARE_GAME_PATH="$1" else FLARE_GAME_PATH="" fi if [ -z "$FLARE_GAME_PATH" ]; then echo "Usage: $0 " exit 1 fi cp -r "$FLARE_GAME_PATH"/mods/fantasycore mods/fantasycore cp -r "$FLARE_GAME_PATH"/mods/empyrean_campaign mods/empyrean_campaign #cmake . emcc \ -v \ -Isrc/ \ src/*.cpp \ -O3 \ -s ASSERTIONS=1 \ -s ALLOW_MEMORY_GROWTH=1 \ -s USE_SDL=2 \ -s USE_SDL_IMAGE=2 \ -s SDL2_IMAGE_FORMATS='["png"]' \ -s USE_SDL_TTF=2 \ -s USE_SDL_MIXER=2 \ -s "EXTRA_EXPORTED_RUNTIME_METHODS=['print']" \ -lidbfs.js \ --preload-file mods \ --use-preload-cache \ -o emscripten/index.html rm -rf mods/fantasycore rm -rf mods/empyrean_campaign cp distribution/emscripten_template.html emscripten/index.html flare-engine-1.14/distribution/emscripten_template.html000066400000000000000000000101521434514645000234510ustar00rootroot00000000000000
Loading...
flare-engine-1.14/distribution/flare.desktop.in000066400000000000000000000016771434514645000216240ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Type=Application Name=Flare GenericName=Flare (Action Role-Playing Game) GenericName[de]=Flare (Action-Rollenspiel) GenericName[fr]=Flare (Jeu de rôle d'action) GenericName[gl]=Xogo de acción e rol GenericName[ru]=Flare (ролевая игра) GenericName[uk]=Flare (рольовий екшн або Action/RPG) Comment=A single player, 2D-isometric, action Role-Playing Game Comment[de]=Ein einzelspieler 2D-isometrisches Action-Rollenspiel Comment[fr]=Un jeu de rôle d'action monojoueur en 2D isométrique Comment[gl]=Xogo individual de acción e rol cunha vista isométrica en dúas dimensións. Comment[ru]=Изометрический ролевой боевик для одного игрока Comment[uk]=Ізометричний рольовий екшн (Action/RPG) для одного гравця TryExec=@FLARE_EXECUTABLE_PATH@ Exec=@FLARE_EXECUTABLE_PATH@ Categories=Game;RolePlaying; Icon=flare Terminal=false flare-engine-1.14/distribution/flare.man000066400000000000000000000027241434514645000203130ustar00rootroot00000000000000.\" -*- nroff -*- .TH FLARE 6 "November 2014" .SH NAME flare \- action role-playing engine .SH SYNOPSIS .B flare .RB [ options ] .SH DESCRIPTION .B FLARE (short for Free Libre Action Roleplaying Engine) is a single-player, 2D-isometric action role-playing engine. It uses simple file formats (INI-style config files) for most of the game data, allowing anyone to easily modify game contents. .SH OPTIONS .IP "\fB\-\-help\fP" Prints a description of command line flags. .IP "\fB\-\-version\fP" Prints the release version. .IP "\fB\-\-data-path=\fIpath\fP" Specifies an exact path to look for mod data. .IP "\fB\-\-debug-event\fP" Prints verbose hardware input information. .IP "\fB\-\-renderer=\fIrenderer\fP" Specifies the rendering backend to use. The default is 'sdl_hardware'. Also available is 'sdl', which is a software-based renderer. .IP "\fB\-\-no-audio\fP" Disables sound effects and music. .IP "\fB\-\-mods=\fImod,...\fP" Starts the game with only these mods enabled. .IP "\fB\-\-load-slot=\fIslot\fP" Loads a save slot by numerical index. .IP "\fB\-\-load-script=\fIscript\fP" Execute's a script upon loading a saved game. The script path is mod-relative. .SH FILES .TP The engine settings and key bindings are stored in .LP \fB$XDG_CONFIG_HOME\fR/flare/ .LP \fB$HOME\fR/.config/flare/ .TP The game data is stored in .LP \fB$XDG_DATA_HOME\fR/flare/ .LP \fB$HOME\fR/.local/share/flare/ .SH AVAILABILITY For more information about the FLARE engine, visit http://flarerpg.org flare-engine-1.14/distribution/flare_logo.svg000066400000000000000000000105321434514645000213530ustar00rootroot00000000000000 image/svg+xml flare-engine-1.14/distribution/flare_logo_icon.png000066400000000000000000000450671434514645000223630ustar00rootroot00000000000000PNG  IHDR\rf pHYsaaøtEXtSoftwarewww.inkscape.org< IDATxy$EuuW1p  "ùp8^z%2" s}wUeTUwVVfdVeQ|33#ODF>|(~z>G}Bg>LWG "q.+g.>>^B7嚙J$y6#41{@ApNgތUǬO%]Si xs܌TǬO%@|$n8gV>f+LWG<^mZ Y'PdXZS|@IBjZ_o#g>f|(AFɯ7Ϛ)Y P n?.Ν8\C<1 [%Ԅ߹K]N>f'|(UYԐ|9JpȄ_= 8U1 @BO[ 6!PZm D#$GmqQ| 6|(ehOo251M brmsJ>0x #㷟g P% >0>Xr>>wc*@CR7<ǬO%)*{N_ ]e';紂Tnj'9H%8s W2w]`"O/ v}& mKҕONZCLܒ'u?dئ Mp2As3FOsR'Us=|o[*)O`'v|>h*{ i 4ݴk>>?{Fd˞BU޶T# |wsZ 0 IeL 䯱Bpb M6 t6NOsU:􏓩),k2_ĸ.DxE%ӛ`z篂%}L!cz\Ϫ> Hɋ4F]uV'9SyP? (6&Q0W yM}c  N[D8{D>xM7*QY_[d.w ``7m|FVV-Un8B'9F آ`a 4Hap ဿlOsv-T7aA' Bp=rݤ\p$fI!u>W62:R0 00ҿ OQp0 uPC.@8'.vJPrOs*qfOD;wKOۏ0C94FJMdZrOs ҁelI!HA,{&|gɵ> 4v(㒉o̰%IRci}!Sr>rOs Bg*~hLOK:X)- ,q G!\ g/: BOF#-K /}+`@=:u=ȱ,?Q@o :ZR'|əBH$8꟏? ΀`5|w5pܦ Gl\C9YCL%:]*K1*@&"J$|!M/΍0R{'tV~ B2%#A!j _w)#X7"z.Y?q-–Ʉo| UdB AKi}4,XX\u*3r@J-NL[9 BMBp3Q3ŷ>{/`wѽ 90/ DBsMڨvQ0'uhrYKlX8qߙE>䆓xJmdVI] m Iޝ>s_[/ hu~7`Kl\C0͎=˩ !h7 dY.jŷ1d[fw}u:\O.KN1?/+g>byDzx!,lfE \m>t‹Xf*Oa}qbL /&#Ud`id-jtL{"ǻ'|p0&' -ݐ>@}a?; C%d7@QHh}'XGT.鬏L աL\Fbl,k k hV蒳s-y'Hm^݀G#dJd;$ ` $UU T;ClAU`B8(A[@5 ݘ7TVY$#"9hXnaP@yUyr#@YdyDrip=0t!9Wuul#klg:oW!I^wkX(1SO'|pPq"6^r L[^6d`0+zUZKT@|h kS]/UGVFrY xo $\")y,2vNf)[qNw  qs][c3kc›S9Ie]t'!8f*\I7M^9h$ wyuAVWP-$0!| .&8*+/u`@>09Zs ̫u\$OPC@Do"౳H*ʲZpcĥ )XXAe)4Z6=0{XF `Nc@sI%3;Ō*.U2KWd߀%!m-k48p_018WHem CVUidGtn4׆S8A IlX^=0-|RpKW$K23EV` #ة<˅dEMe3 $Zk^]RYQlω פ--!0"?ѩjz4)xY@"`RB%W( RnT8|~wɓ.᪩hXG$qauJ?}:7vt.}2f7t jpKG>( `HK_F}+uK5ysŊp#8/}:"ဓX[.ag1IDшn" #fOsݪDC+jKCH5,)D7^?\JyM,YX s?X_D,@{iKy2O*%ÕeNYQ4uB_̵^</nt 5;}Iv`kX[vAyOt~^ʇm(P fKcW_duT4sM/Pn%Ky%\8ZE9lyݤr6B`@P܄q G8Ph_dG\-8:uhn|{ph.ԇ/I)لK'VcC'ׂ0빂 (-s7 ɐWj!ݍls5$5/W?#Q˃)xCAՓ&E 'QWD)]#^O3r\ VYl38 ,n,cӄ  AlGU}ȶ QIZª MU04 %c24ֆy>GT!gC}'J(h`i)R8ȴSL .HB:NZK5w3 ˩9Uۿ$>$2`yV.p05׿a@R]YMobй軗&j $$/ҖEﭫpMHnÅ:×7L~db;pgoG&28$ڪ[S5,BSqڱ2#bQƬ&߽lx@~cޢ `ӄ҅V]Z9%dq o"͋f*~ p[+01q/" Hl LTi^^Fs^ɲAAH 6NIfm~)$/ ׾w):hT:7~3Hae4V\٫9:*+\lA gcj"&[v'wZv݀,(ޠԄ wu0>ʩ,lvrcb| .!t ^N`yPP,[\́1trg13"DSC9KPS1!lγ*b 4patYg9 1]?)  ݻ:Ȋ*42G4LeaAˀ&="r979:DIN FkCF9V,**mYFٙVO)S?|ˈ|Bd#׆TkCK,v)յ!$~ 30U7jB5@$Q^:˸oh5WE N9t20sxC,b7^giUU-⹕hkR_ !M_[Ҍp2˵SxL`sonm r͂ʯ+i.@}\t>'$jVL`HSQDM*J+]Bpj8;,?ە O/['U+7Ok% X/ ysrU~s}$ ͇6Gȹ(QpyB w*E:R`CiՊH+,ʱ qƷ\_n',b]_7ʯjmg47xbt%!]|kz+EU|eke8j-h3agV^ciy Sy{T$f\rƴd;S(Š~0oñsGJ^PE\ZԬƴߜ?&D}$bq hs/g[*Mfڤ7DR/G`Q舜v{\3bJ9$`8zTh,M$I[!C,;oGa6 +EM!xHn~x2ɷ.)ٜC`ãW|K9d$|h%m*.}nTpWaϭ|y a dY䑺t@0i*:Չd-'/c/ݹĮu70 "dG[ @XVvy&2˨1ȍ[ienMe!a!=vJ"AIf_|'~`r 輢wd`V-~ezumҘsxvu4?k ERb4r2<-9|T"݀ $?3;{%Uc6>;rTS^N/i62&}$Q\^Li2tVu p:E!]|JG9oWŏ&2ȉe<0mPfvg"E r} r7oHMN@f. ؖgr ht#|v.3UҬTljssijTO~qY6FݥиS}? ;++`v REo* J rLqXܷ*KUt1~;{;8>YmYnĒwɕekL?`Gcޘ IDATzU>gg[=#tF.yyu=Ń'Y󚇄] ƔuIVd| ^<'2t*`!#IdgW9[򬣍%xyu=|֔ `yAA.QU>@c6-a~t,"eY,JiG\ViUaN-;wk3|o_4 **qQzts'ZDpLnHHVԐwwo&f9[@:Wߐѝg7fg H^h,n 3f'$; j2(DI62T:MZ<&vd>qGC>siҲXleHoCΐ?;J-֐W|~h.ޔY5UΡmeN_ns^^fŭv-`87u4,|TZȕ+]z͟{+}ʟ'^5/QQ1D%Ԅ.00}˸os`"iuwv@[}S:uyY Wf&Ym"oCJMWQ^G7/GϢxQ$dr@KZ#i˰* -{/@U 묶&QzMaOT$`sT{'`VIlbTtؽw714q 's>yQ =!lü{1!]߸.Db(g!SR[Z(s[sَv$`P|cڔ2*?VМ2u]ұ{-g=$#swy+(I]NkBҖ2^ٮVs>F%X]N0T&ª+UcP][9X XoCn3X$=!ٵ-[Oι?zɨPٞ{#¦9"{ CO'# ޥ{<:6 Zrs2-a # @`9k%63+0.ٹ-z4uܹg/*J\CMapbM¦oV 9:ϼm%wj]jŭHpHkfS~nB Oc{[0>0Y.|w:Jn0^Ɖ(+ٯՂ0U TWf S ']n&~a#oxf:Hr+k Z#@\{JZɡ%")Yz ‚L*ް+jgnmk7V尳&f+@❫*ҲV<M̯Umqx=h0 Ed-3QR P<26FМ>1&B]cs jVzZngnYE0U !ho┓8zEѼ8_S TʼnW3.Ԁ|@c Eӫh-:[T 8G%oi4d_ks9hT+¡PDb^Uਥph%5!/by0(j9y1hk^X~Pwe:4̬6-:z%+%qLŽL^C*W+8ӗB+@S]`P#יRAu% nLq IzTPlv "4A&9hus%23Ԕ`k8{Y9 xijj85(Zxa1DZM45A<秄Q1$<^˔"/K:`3=iI]}yXr|*p12#91@Z# `z;@ZڶrjʩH1$0eqiTsn~;]%Iޭط Xz|˝t7쮧85fH4qx{MAwIdnnM)h @Ju_шk*D9(Gse]CLHn֗v2ܧ1+z8C:Fu 'h!2`\0QYHpm_cu,T EKȦ*0Lsmc6 7;] ;{Cc[-Khg+ ; Վ\UEBGE9˩ q(ʥS^'~kL{Y x+\̥} ^~HYrP;g\.μ\q6o8|=i%䵜HYي*#,Zȼmqyu:8?ƾq]-ذn< ~~5IvUK"SйxX]otN1O?&ܰvI䵜%s\ hY SSdyqVx+l;qO][|:·uE@(?.oaysoe:vo1o|4Ύ7:E"n? Eo7urI]*qeq^{yb U!ңJ+V;ga=Uas&<1l@Yk_v7YPdZBxovsKz›K/g4_R{XhIV@`vt`JA(Fp]Q!VivRvt$-]֍f=$ pI˵.ȊYj$~_8[w ŗ\jF5 (LeDPZipӎٹ;z*~|M隑YAO!A8`ZcA=w_ϝy7 Oނٍb$>.ea l?3H[ٳm|]rnKFkYI>\R(}A5 W'tt8=(sR3Gi\R+ر./`.Q,%wڄ~F!Yοk @PJA%ԡk {$OI 32*2#spN\P\t8 1vnaA_vF%|RQڪhoJ}Lւk蒮Qw 1`xƝs#!Eݏ_<+a|$Vt##1vlaϟw_ Hn=.dä\Aϯ׿n^$k 1eaD yNܰNFd^ ~6i'γsk#= +c_B~#EOk0, 0A-YtnE}c3€w"pʝ@HQ3q;p]|ca-nh& l8N?6g׶v' Vhm{_ 0}a -U;f-o&0z$B=%61( R½v NZye RdO~ӹ9-VyX<%ع D| [NduD)+K#2n `Qi'$ 9NP<O}&Ki$m`ԇA9Q~XeL9c:w{{%ƓL.y|8K,q^E§n_s[&;L%g3[PyRAMAZ0gw"at.ΞmtlYer[ *X ¤yd)n~$`Mr>zъ EK^fa[4e eJ82@2>ٳ^ U5e,X@}CͩUoIJ J0yi1/wDmjԸ_`O1IHhjJIyRN*ɍCSY8Sڬsc~sCaqyä$ИEuK.ǦkYچhvk$`H<1Im0KӉ)m"Qұ)jCQ[*BT[-%/%`JRcki_\O}SEϡH_/uk#t:w~pq4Ŋ&u)JUengB݀sSU~k;u^vmbl0DPPe TהպN9 ]{yl5N:6EtZBڄI1لƤ&3I*?S^hV f'!dOtI_q?aHge w]1'$疪C XQ dV܃%MsSz++@e XLk 3@K>vm-\oFm}K-P~V|")k k\}59 5Sv9&M8wA9{yp9i:ץsw/;t1Z5gX{aji_TN'F 'p$#|K[D,8Ҧ -5A-c˟E`+?e 牄ξ>:&U5e,X\Gm}4۴&tE`i9X[ ^:L?>Y JREaPV74%E\";zyCr"q^2`Qu品J?}Rx"7$[w1uZUwr0wN'"öNCh45!Ӭt4K89Gc0KFtl綱^?@O+OƩ71^}y/|}ؚgmU?su[xkC Yc)nogO٤7o%w@0)7r;~^ғ楠hEilp6~Vaϕqyb IDATշq˽J6sBo*P@p0;lu^@,`OG?{;~< ܮ'锻&qHhj+ S^Rs].LtiN:q{bDsA^[# jsuZ:&ecٷ 75'|2x".^*RVr >:N tƞ7&xۉ+/T(9*qY6fe[U>'cAtokK"pq2o7pޡH$H{[%͍QDzP' 3(~"!97=#kH |wT(Ix \VG*L;FScs7X^)+~/a{9Fp!moMX ߁%}1:{&L!Iw|] (YxdJ. ?[Ythݻ?e%]h7E:4> \"he^S4Hh  93Ɓqc9E(X/5$FLc~Kv2 s~^>,n 3Ω;`81vϳoApH+w^|W`5SREt`;!):6,j Rk ?ci ϝb} r!0!m}z#,͏o3A5:<ؽnG)V_oR;#> 瘼xXssKB`Wífs &߹MSU`IK9e`:'9\oJ:|>ҕTGyH&w!>bc a8{;G:v_+>f|0`/tr+(#c w*pK"W}o\_GR3î TFyY.d02`B 7 ' XkM*YظΞ:@ йՇ|PA$" 3>BE5PXLgϾaN$;=ϒ*7Ѳk"hX"(tvz[" z|uR<0"څdR,+4t!ސ:Ç>|Ç>|Ç>|ǜS ` [IENDB`flare-engine-1.14/distribution/flare_logo_icon.svg000066400000000000000000000117211434514645000223640ustar00rootroot00000000000000 image/svg+xml flare-engine-1.14/distribution/linux/000077500000000000000000000000001434514645000176575ustar00rootroot00000000000000flare-engine-1.14/distribution/linux/flare.sh000077500000000000000000000011411434514645000213040ustar00rootroot00000000000000#!/bin/bash # Flare Shell Script # Written by Ethan "flibitijibibo" Lee # Move to script's directory cd "`dirname "$0"`" # Get the kernel/architecture information UNAME=`uname` ARCH=`uname -m` # Set the libpath and pick the proper binary if [ "$UNAME" == "Darwin" ]; then export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:./lib/ ./flare elif [ "$UNAME" == "Linux" ]; then if [ "$ARCH" == "x86_64" ]; then export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./x86_64/ ./x86_64/flare.x86_64 $@ #else # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./x86/ # ./x86/flare.x86 $@ fi fi flare-engine-1.14/distribution/macos/000077500000000000000000000000001434514645000176225ustar00rootroot00000000000000flare-engine-1.14/distribution/macos/README.txt000066400000000000000000000001251434514645000213160ustar00rootroot00000000000000DMG creation scripts live in a separate repo: https://github.com/flareteam/flare-dmg flare-engine-1.14/distribution/macos/create_iconset.sh000077500000000000000000000006421434514645000231520ustar00rootroot00000000000000#!/bin/bash # requires: Inkscape mkdir -p "flare.iconset" cd "flare.iconset" sizes=(1024 512 256 128 64 32) for size in ${sizes[@]}; do /Applications/Inkscape.app/Contents/MacOS/inkscape -w ${size} -h ${size} -o "icon_${size}x${size}.png" "../../flare_logo_icon.svg" let half=size/2 cp "icon_${size}x${size}.png" "icon_${half}x${half}@2x.png" done cd ../ iconutil -c icns "flare.iconset" rm -rf "flare.iconset" flare-engine-1.14/distribution/macos/docs/000077500000000000000000000000001434514645000205525ustar00rootroot00000000000000flare-engine-1.14/distribution/macos/docs/Flare DMG Build Documentation.odt000066400000000000000000006471341434514645000265340ustar00rootroot00000000000000PK&7pL^2 ''mimetypeapplication/vnd.oasis.opendocument.textPK&7pLX,,Thumbnails/thumbnail.pngPNG  IHDR߃rPLTE ###***444;;;CCCLLLTTT\\\ccckkksssvu{}vv|ys{{{kq{}T[afjt|mmyrxv{zz}{ㆨ勬启摰疴虶蝹魵ŵ¶Ⱥ¸ΣļʵҼҦ»¼¾½ƾȽќv(IDATx} t}n6ؑc[T&1a]>^tB}*9 _(zF(7؄QRI$qTP@¼iT(y`"ɼp(G xw'rnnowgۙ66mEGBG 4aqq59tp+: +5\k{{jWpRJ5IӦĢfM\CMjZ&lWVZ\CQCVlĴR&n2UyC2$[fforZ67[<ڼQ7Y+jHhhkCI{lhg`Xmv5Tfb&̓КlV5YL6FfќHF{ 7)Gl ͊7[-Um66mn!a'ڔӪ D6m[ݞMbc7 (nPq&-7& շŋkJp#zEGi\A6O@20&h^AsMS'P61J$4^ހEs:6)(CCnv7MQh-DMa̛<Ֆx㴨psoM5AֶfW4hkob\% ?YnND xI 7Ls&3=a ƥYk.ta3'IWl*Sj%K1%SWò70ncV6cL,ҷj^q[^gХguݱ#Ɨ.}` jr1A8D2,Ձ҄0%o{ȳ;sc:c?ȗs=>c_ǎ}ءgG#G{k#HJAzأGwW{o>;=kv"24Ơds#L(jQF9ER:v䑗#Gcpyc_~G}ȳ>v=zȱCt_׿쑯{~_-/?x9g[KyЗ{QsClt[ilJs%6#lFץEQ+oVؖmIIn%ICv{dmmu0 n M`$-bl6D@~J(ns]oś|=}a0WlFqgD30Hn!۱Yn{M0(RHjq 2){,J3$b0B NBvT рQo4ey50j90bRXX(W)WKP,8[a j~+0 T9:BΒ8A~(^|v\&a%n(BB(:>} T[} yVI"i[yYEC,y[^j o%( ;ط,۲"J66#V!par. (J S~"I|iVҶqCRo'a/%zMa,f[qɵÈOQqT]Th\ȹx>KSNT|7NVsڟjdKceq1}P%=EID"G4. Rܥ>swohhhhGC&<DŽCZ. Ț$m^ͼ= Y$|ެ# \ B@(ެiܖ0flͼIf;mT{m񜨽mx j;]k#6ͼY4 lKhdR0|۷ҡH6x{FoKkCpr-&&ɠmhXI[-6qxI* P `،haФHeLp!5L23Nui* bBCDS $%""ahELR!4T|@+BJ\’9)2-x##pf ¤C@-6Q;k#\Ƕ=qBJYqv}7pCϵ] nL(- mkP[IEڄ֐$]h=!8 ,Ct0"EXAp a2d(R(W8”b##p9f1(2( ˆp$ :`!- @t !iQ7Kשo ZcK\+nKrM/nF"< M(f@ecYLA # (v"#,)@k 26Z EK!!  B @(D7BC$R!J@DM0i]dB, zi ctdR7kiuEc((CJ"ƉH'ȷ WpXvLݵ48T 3Mn1e:3 W ɨRJ%U pK| R9c} |hipP2Fr@0D3 k8F2[#w|-r UөIpkVbpT8D{PlVW>@9]בر,\8s[c6ϒ/#Uо ߓHה!PfpX!$EA It2%5 46 Ʉ()Q!xcC!Bp!ƌ %I!"W"zk T1oλKbb6L}UT5/pi!HlV&b(R= ڸo6TUZO@[E)Bh{?Q(rq1sj@E#(M 쥁MaPW,dE-#7Ձ#$Yh1`pm?6lp=]h0A::L!YIz1j[X9z`Ճ]v<[:iycAm"lx YPƀ%å[hTq\4>( 1]+fP`5S!IW̕B I7G۔0y$3؅nhhܼ?kDj<nf{#¡*N $F_~`m;s?z) $o;U='w?4}]T;vzN '](O.@A<@G$P'P=.u Vޮd3W5Ð:ZU=plC-UEL X+ y{.k L^ΔPHtnVjY z;gd{Ctf 0fxLS9rfD! qZeVg|6Dg녡WoЏpD{$DתAh5z#Nt/ $#K[fAPw2<>~6MIV\sRR蕅sVTMo5% o+<˂uc䕗p#;xr;rқ,Hv?]Brj:>:tE/WyYg?-'Fn:D}s?[yswdW}tdJUFYʎ'},/>Ak/O+3%p%+ 5g5uIWVJ*zz Ц \rȖٽRT]((e>,z*Ji5Uں-E.Dx?+r8',`d^fPT^Z$;x,L 糬Nު=@F ?yeZ] ?sogǶv}ۂΏ|nk<էjfO)Q|gYFK*웻M0zΜߧR'zc m=1yi49#@qy0E`q][YZ LPrD~]E');v(ܻ 'fީTn O8:d@\TMwҡݔI"t.~t3 Bm Ýzl 52TO?M=M&1]{pz u>3~':_nnolkfEϵ[Zd^6k,ۦʵ][7 hsO}R {b/l!'Zĥ d2eKG⒌L>h;x rub:Øxc.uhsw/cA=n:w\:4$W.e)w<@f scua),e#˕"8p&KK7(hNuSMf5UY47SY]+pA^qZ޹;8yG\*sKpL3&,;Qg?>NWg_|C:H}N_م?򣻣R ]R,J+(:Nt d.u? EcU$t\^^cĜ3X\{̗פrKj\.X26w .YN1TW7}w/Ky _iΒk7຀p{3͙ӼP4R˝AfqE/ʰܢʙJE3!Vq)&T͝{5|J{v^DЗk_,<0 ڟe=dQ4x ɩ3}=}9If[1ڌ3˗/B._g+tu5]3wifqn*a ɩS6QI8Tq}KJͥxJumi5ZRkzN?f >>?oߥ%h0 _ ey5QIGa%T̤3E9D iQ'W!UnC徳=F>{O]_~1~  ڗ,FD*ӥ>{}W#Tg/y(zvN}Lf vᔲ"3Eh>7s# VH94Ȗ3,[11gC*~>_B(GYFߔ8ʋ)Xq lWJr`l2[Į+&KgUM{eX4-x$7wsQ<܅b5R4 ߺ,J$]u)ދ{~Χwnշ_vOһgEI0NwQNӫ'Toj2͘)Mz0Q9T=Y;S+c҄&U&y{GV &6tF9" |be\ g7ϲ3T&dˎ{ס6 RfS篈'N(gPf kPEW╚T d9 u3Ӱ|KnF^c*Vfk))-ֿ alVbtR-mLr--I*/|qw$k~`org24LcR2z;:ݻk0ӏҲsݻ| Fvu蓮&7@;<|`#}g?EY^RL Me c9E'(ٛt/u@e2;N9_=^YCO^onɼN+5jJOonf:;LooO'ac||\f_J{{:Ζzz{\)!:!dzh2:vfV CcN mPŠlyLE:K(gi6|t%60Zs!vKG>sQ| VxN1ZI/pzvc2TGfNq\\3{%% &Px72OyB9wo?Ʊ e~qk4Z{Yk&JRmk}iX:/bQ\MJ,WFec)MU2Cbl"#d8^\Q5xT%>,zqR~jձmd}:v/v.%8E23rџg1ϰ1Lg2Q 9E_@Y}aL4HnH.yw/7R[BH̋窤Ǯ,}c #9#r6SnB7&:GB\'iz>0P^/ʼSV[/8f_ȗ/|ɿJ߅e@ jޑTBh5z}A<,_ަ(VOf_̯NMڹ]HM2cB]=+Ii^ϝf B7Lm|C~p+mN^>a<5>RU, ;;~f_ <4[^&/9mCsRj@JkR-6]=gWI%8lVOVSkOfZ5@B]<9~$At.k~}\?7R>V6_S`.AK;[G^D'rbl߻}?w:zc}Qaγ>V tbhAl cüvU$xO(q2&ʌFN;ONDm`,RI߮˪痆9 iJfpVxDO /)* c "gzjA`uK%7~㗅H3{]Y+?63ťT놱ĀI0wWr,xWj+3>T=)n2q0T׹8ő+8ƿ)1iRIpq>8.G107[n}!]۶}bǂmm&B|]u(3ɧFOs;DeݦCA'=A@a|L澡,5WmI=8x="3 #RNEQ`+R\Yw}Mm% z-gcZso6 &kn Tǹފ񚄹l|͌6+Wҹ)(+L ~Lr~5 ?dή%smXoGַ⊥`^+d&/)<ϻD5Py2,k4ELA|~K6_=nNs=ױ"sYR?PIl9-ry6;#qŸhv};"wj'⒌mc[;{wGzx}Ӓ`*x( [2]1Ҏr|qV` Q_.HK{\6cs~aQv]=·>4\+7@9׍|?Cı=Y?xw8 2\1 å@f gO]жk潕N9pyبQ9& +1C mdh5/;fq@ $7 4i-ܵ]N&l%Ҽ% ,p&%rm w(cBɶvz8I cG Ƕ-7+ v[GIKM]Op^rȑ ȼ &QP >B y 8K#įSC0]KD# 4yJ ؼDkT9k}:U:uΧU67;9inegX,Z7~2 s N' (xl{>>Ψ44bX,ZB! 'O)8#(&=tLX,b OS422 D,b^M'%| DS§ei}o9>>,ZwMMMA__x^<PQ0Jlс#PȽ=xFs|Z#!ӧOX7dy V7+lu;"n ~,1 eN</×oK>_<>[z&Qa)zq>|zAQ}T|홃#VyOIJc~,~yFݡ4pAKކl5Q-bpWK%2X,j ۞* F 6:P^G>v#9&>oGYZӨ8Ac2?2 ܉nI|s^/00b^y5441VMo6o UtWOq~*LhЛuC exk] _N"\b_λK xy}`{wBcwgd{ؾqV  ŲmD"ѬjXYṭ۳X,f| DӁ;OWOI\H^wuߩ~}ES O5lU着giJز~?|! g9SAR2ŲI` ;.f?ꇱw" q WgRXXkoU="/ԀG2J 雷e93k xC൝߆>8<uX}hgdL#pMݧv*1⁡ޏ |p電-Gem։ _)mL#wg+P}k^ >6+ϓ;`\z՝ͳXMvm>q(4A&kv~-;vs( A9p@ԖDOl_%?~T@cNx(뿦3~ lۗaxOPV* G DƘ imp0mL L252cUYu\/' R΃Ԭa |E_!TT%bm6}0<<@Z=L֓JE}BOOW$tEV2 8/mS?m|xJޗ~c2vle(PX,fS[[ n z`m6{)|޸(oTo=}*{Bm{{zimbmJY,mf)0mxbYr mcuBm_&96> =쀿@PM}XM* ~bXW)3>8D(F*bXd{v;8bXWRF}=E 6:PX,b%+ D,b^U566fmާc,"MO hoOT_\ɤ@0:ҶXzrg$n,ڶ{AOOlx}YCd |zo~vgVġ:xn3ap9ك뺟i[,cRlw>|'Nk~s"#G?'aekڧq=OxosSpo#)l7۝O;vbOPWW< z`~~Tk DkOccckV3:q~Wj?۽HQWWLOO@ZaS@a@\:ˎ7c@ڪ@¿0lƜ|zᗿ% oO>!O^vѕnxEZa>3^Ý;we b ءe|3itO=Oaddg>2\g ~)?5'΁r DFVku0c MCCC? ӟ|2DZ@T]]  9Clй|rC߇c?=Fz[1-7P ɶ/*>zޔe>~H$uqulO:ֹd6=npIܺu+v~{{{;:F;a,DZ$&윞={R,t|?~ gug8`>ojhht#155EFu:^,\--mp6/"k?ms ɈPt§ 9 l\(-a'}P VS(jdZi3~^v-+Ea Uol}(F֗|Lv 2':KW`w6Ƥ6'dУᣏ>z)voe:!7K{^  B<g[e/k\5`6|E@Qk Ec:f ZN OsA ',Wi-H~-;W Ӵ몷uֶj D;99IRו2-`lq6f;]4UU"ls<ԓ za@PSmy&!E$t@lD ʥsD)>,#V@o}S|d72QR7G2ZYos LR T'ᖻ/͢lJ~o^x!G/g-+:1v=V-//G U4Oa. "N,#;|B<Q>1=_BUlZ1<|.gVUO&0Zr{㝀Qޓj=qۭ\6?@ [qFE7y?TVVg}ۈEor75!>AK D)G~T^2Quۤ>zǀLѠVؿ?[ӭLv"%?-Deeڍޡ >` (Z.͢L7No^0[Oz̩٩ hjP'g30 =Y~Hx݆^Q <u<B=y|{1BupC k3=Vm$gz=~Jd[I'$5,7@_â1bZr4S'LvX)3!z uq{9w6/ ^ ?yBS˱{(u=(pc]pٹshʝs|1 J]Y}WZ" =Ѭ] )3?bm. 9mL%ʝ {噴"\2:^jL!|ݙfy6xu6鄐2[mCӫ!J׷VPu.Fk)|GOB; F2|Q2fƇD&70B.[F #0;j*M6%:]S=D5]nZ^2(} ^&EO;kDMy~֪5,xSmdxHA׃@^7ٖgR:ZQ2?/D.0uQt IF;\ e3ʹ-vh>vKVv'/8}O"g1ؗaf Dn^6V{^R/P4{/{1_}O?e-%^h =H pGh]]: 5dVk>~F@@@ LŽuҥ1LcRPM2zWۜ#Q2Dy rrlP.n.#Y$tu`R=T\ e?( utROwu/ڝIK\o7i[^/fm^ݛEٮCeQFlYGgƶ= w*+)070}yUDa^\UGUpGMC}]Us5QO7zTuy]w5ɖS|4h~Q&3ѣG?zZ\:ϴ/V7]閭mv3aJ0iP85ofZNeWʚpaexDpx͵@ GDDkU*W J}g/em&sW^=_3adFhrsfzq+[pMhnnmi(s]h>!U]Dl D:ޕv.n,zԔ3m. l1n~Χotq`֧0L2 ;4^>;>gϵҁ#?ifi1陸skߘɳkg6-ujv.n,ڷ_{D8u/7[{jbM]wGC5 (*ӦU]\wVw|;c2k_c?vo%q`ֶ. 2)Z: |_ZbX,+7 jhhmCȬ NrbX,+@}}=bXWW2  Qfe[: L=ebX,ݻwX,bvZ~@IـihpeZ)PX,b%+D***,ʨU՟bX,R@TWW۴e^bX,UURR@bX,bmn9N>,nӬe36@@bx`M31X,,kcjJW7A[ ^^wr}۟'w?Gwyjyv))/G,܏oN T.L~wH|l_:PvŠ1Lt."I=Npx O;|#Dq՟'rD\U+RyuU BP|꼜mս.DF@#y%z~Q=Dx'^woycIt* #*µwkUwp}a;(Ic^ݹs/Ѯ` oKyiU_,v j:'Zc:Cǵc4Pc,Ѝ:lG'( =+xiS1m,]~?7^^NJ1(V\7GU^/Q[s러IۡT52L!a, B,:wxcuB ઀D΋!h^ePKpU-_u嫠;:F?_$,VUSSyΞ= P>7._+ \iw@ŵ뱾ͯWF|D_u#]J,R]ZacYNKSō_lAFo<\1+PKYC͓zdѩ˲%TO7 g|1`m j9Wae˲ג|BۤmSLmUf^oኁ<͇ <w#_RpBv 8w t7"BVhB錔<yy:ZTWU߅Q_(,֖ԗeoK05VF{' tWoBu痽H u$T߽+j)q;DBA ƻ:IF!L<) dh=Bv&Qk _kVvVmn徼ZD^g ڬ@wS8pA7p8 :n)~M Zw W#h@\4~pVh zAo7,ț#Msj+^_ǧ&"aZr > ҄r*Lr;/UTb~ҴB#>I8y'>"HpuWMLaSwqZ =], V A?SȘ7eo>yC'4s\xlsQNP+M ћ4e6X<(OJl-]p801e!;SJ@"]HA+<:Q~fh٠E6IcX,a(p8'bX," %"AQ(bX,kkO0w`02g$?{'ӭ3I rbf&%6K3PY-&z)]8(< B `82˶%)cXqis[e>3`1λN쁽⻱G#lz. ŖIp⑄ \?R`upIW^OxMz8}4;2EJps)OKN_fG ?n3t<,%l?^Iyreؒh L2]!XB-;g.(S3 y-Tۧp--xR1uXW{Bւ7:Ƅ󇪴K˩CxX- &y 2d(kjE(NR _')8.w]F}n/[e']r:ڋ~8"8c{ÍԅŁ\^?HfO /b r(r!rOs!u̎Nt4ڃq{Xf@P%ۡ%t]JtXA')lp@h6V%ahP1@%v 8C+OsPPPka)j{l)!hmteTF(0 20+f;{T-Q ҙ)PGvku$Q;mP zmtcblH@X3%<5ax,% \%cY6  "uZZZ"He݃Z;#xf'AGn%MIPO,?x@2=8uNc-dcL"~(Zl>s/YO( b^2*ot(B>zbw%:=w) ``, x`W Ku@Wij .ia,cA̪KpC6 H0M IРWy8/eڶ4P\*x(iBLaW- .:U*YGJ7:u\^8ӆ0QtTy ®qc'K\U""6>ڊr9]xk]!ehZwfiQ4#i4u w\?%o޻)~Ք֤*zV@J}Hv(8/+ 'RW(,%jFDA".AcƁ\^X%x^DN<>;n!"(+#+zt<&X~}KN naUKf[-TU#uz"앁dq@%b%T@C#)!;a! Nk|Nt`*+Lfќi{𻮄, -jX:vŧwPmKOy4^475e5{4KEmlNךֱ5b.gvVR/~cR#pZ+=gyQrQnkQ$L\Tvqw|JL+HK=wD d6bvjIs֫f," .\fӆf{6+@;֮īHJȡ< cZu)$Ny,)1D)qMڠܣ4ピΜDXk+B-졘vձ :*NW(]v 8N8+V Pp8j_whlOXs)kM O@ C{IRlF32{ʬb)3%QPyJxCD<bҭC*lQl%+< 0,ǭr̒ n&?ef2kM"heHA)d 6 ؎^)OY0©@DO§BTl@䩽 42<Ӆ郪RF ")hL*+"ϜSyQisF$qĉ'N>! %1<< "N8qĉ" ́qix=U622}9N8qĉEOҫV+n=??s)F?B[k[| 0'Nk氏[{9o^\ǹJ\-SVx̦W\֦͐Mn „!I߼999IShEKKٮ);vӡfx(:Lvm֎|=v? !;"0,O-Gco= kD(>#itD^ڽp@S3~<2$ 6iat:upEYF_BrtEVi'Ttb4GZu% tczU:FQk 5#.mIk:t]qdЗ€~Ӳ|P>f pGkz }me]UtL[{iJ P_jiI\cm0gS5ڂJ Is6$zU E1텞ꂎ mi:pʭc0?o9A a;;0n'sS(w ȓ=!΅ Q]ƾ)X ugv;=FECk6x {;M6H[Ӽ x{Nk(_Rg(J{e,4=$ס@뜓|bٕ&]p 7qn_g7 K  $ ^Sjq@QRc$1 58HTXaza d/uS43TVhdЄpTԥxi!SٹX 5絴I텛z&ꇢ@H:+v8O oiEм v躙5~ ! ی@444Dp ?={!0giӋ6) _$->+@!P3a ~gq>[AFfr^ h:KPTXL"ur Y bay&@q7ѝ*OҘX{p Gw"1豚N(pT G:(8'gϪ֭sf;>) frx >wGU^7L^Ӛj?Tu @4'Xo^@7oQ^M"⬇/x'kP9VB k2ƙdJ0BUDpC6 H(2C`!BMΥA׏.$K->k= @v8 1O[bJSjK^W'[#7:عu"ry8)4Q DAR5X5ŎS;Pt) !Z$Mã4mR9M>q 5k9jsCtٌ@s}?Q8e1F_|=.ihuCq\w/~ ksaJT_4.wߗm9Z, 4ƉʁHM}ybXo j08PZ'`\% Ubr']]%J@^]o Bҝ!%E%B^.a@( @F/^4Z6 9ۖJé$M-O*w&BOZ N)6`lJ$ʲ&F<$S}d(A~o gwh"ᾊc8!Q_9-U[D`.r=Dk|Rk)& FR`7z'p;_@~5)3YAlAc)hJl"> \2E! ؎^)OuR' çW`fGe% yjo,W2<]5}P;dMFc?Zdl>\شs$B>7tk+0avNd1|bks!RA`Nzў&>H,:X C)"IԼ=iCDžIO-B_٤AsJ)ʤ(/5G}vq7wS)Y_D{?MO᱋zzpBy-Oٝm@v,S\^(rZ@tE5I)Բܣo=Tiӆ"sXUOcMgg'ٳ#6PÉH*}N/iĉ+Djnnup]NyZz 1i#9X'N8qĉ'N8qĉF" jfX,0@O->}J/e w}:?1 B'|Y,bm —-c!5|O?[Oyh>u%|Y,b4 ?P]O ,4g>4<}'Oep¿bX,ч~H"&[s{1@XzkiLX E82Z/@yvJ67 3 ą ;y-løO<ÝPkdM:+vX iNk c'oFW۹JEWf "^4w֘ԎwR;J,Ƕ`2}r5^@P|lŬ@4{FF6ID{Rh9 9w[Ipn8r U.ڶغ޽cY=:(>W٦e{=~ m_0ط}9P&vv@MH?\?[]dOߍdn=-P@?ϵ=_ oI3,)1D]J2JB36vo2'>QZLe) Dc===9蟌yεQ~,6Q^G9=I.xb3Dn- BAui拙^cN":b}ð J !wxNeNYʥ.@ui7% PQfKq3b7m q ôPUS *^(bY6R2m`m-a18l)a2yQ(*3(%:<872X;`'9_uCⅢS"{@dL!"owe DOM"s%Ps} ʼnʀtwnOOv'S'Fu`RoGuqwrM(VMm/}*u )DgGuzHO0opi,ݭj :U%6'+8KَSq4M! tS}VoKD!jFžD B8=v%@t:qTIS`< gBWN8wO삞gW<$3I`,iZtqIġd2E63#{d&$6T3c3ѩ; { N!ٝC٧o(fw*Ί7/]ujMwLmҾi5GQ(J[cuWd]y=KA(7NWne_vBA')*_Nc|UW@{:Wc4_k$5d`82 UWTuKa@J_ZvœPy-U>OsM+Zpykc%ܤsX}NĹƶ4t6 ЋQ-T49h_ecL_|[ PÇQr+Ķn}pS0F@d߃>ZnN7DTމ@)P!/{l NG`rL%SmIMAD_7e [-I!2A\2؃P@iUȭ@)@G)uW+ϧ:zhs7Z*]iin N M+"бaiaZ[DЋrJ-nX9B(`) 1$*0 nŗ玺\gz+`4KshB8*R<\svP mUT(TeaV>NJF{ "* W @;O; T詽='6If"H+.7d6X`n+x%it@GV9v(bH?Ӟph$1-f,[,Q']|Q ڱV[tcިHbGnS 1/Sfx\G߀7+@y7T_=Lf5dރCgM`<_m,T܉=ԩ;Eξxo}5l&:w &#ܱj?Tu @4' MC+4ݡ"ʫIrYDPT5 d J>֊RPa|M8,@iP Xn t ^"!$D_AeRGJ N - 1)^% tA[דGbi\ :<X ¨OJ ,b) D^@ dS/CF j[Q_|Ufk0Rt \EwoL5BoQv F b"5"Kikx4ݗ83@ 8%jʭCG!:x{L󤽸/܎&g!CV &Q8{ j: M7EfO1QqHAyGȣN!3Ac=s;…;e*Q :vI:ܟ8 /"\MT]CR K^DJ CNːS(0"l@UOPaHr}ѫm jK?%OUY4梖$ʻ~Px YN2(P/C]P #Kiω*JSMStZ m~ߤ)Ү@7B+zm$hkGTw̿!ڬhcN+]kEM=CӺсy~8t(=GlA<sA\) aMe"*S"=&pРV(ۙѡ;v&2)rM"AX y@ެrwBM%{dQ,G׌X/($SZ 3cQr.̝*YỹljjErPDsW(ZuG^%V©)2"[]Zz,E8T<Xba PєCq38"pN/1XZߌ{yA˝)&{.5iD zu 04+{-$k\o s4e2J]%O7*HfgBS7ɤ*>Xڬ@tKrh #ˋeC"=ɶw=2 LRG2zǁMu܋ "XUFEŖSL0eA&ByvQ$E2֭DJa@&SqZ; 2Rn┍gljFrgc4SUo3uYh z+H~G遪^)lY^eAM1s>)J\(zOb2MMiy=U_b$"h=K`IRyFNΧm & )|!HurPn1$ <QQg w*oڝ1ծgmVޤ3yɇ,Ҕ5%(}rpj-@qqwj˃zJW#Wvz'!rkF^=e'LpLE406d>|L?t 4B |3]Cy5ldd>M,3c3Djc|̃=~Mv6vvMǯY L!5DxPղ彞`pui tmCA "V"@` FpDmb5(~=yo|advրUGeBõ/'evbK;n;E=^?t~Qf d13uHU#uHa4MN>}7,'/@4e^ T7\ߍnc #)3%7:V@װe9R p=TDF5D?D)R}̀\y' /NzWstT*s2(ƹ.3"(^p.D'! ^rk:6,!^(k8چ0a-3HcMˠybN'Qc&}8ү# 'ß/KOcxVDZ]Z8~LJBJP'֛ H7!tĴ4PK{tSm7 *u!ڹu }T7]} HX"j2.L~]IqA vi YѢ^)N' ACdưwnq =!| jX(DcYG?ιÀѽ{!ufuW#_; #l,up;w@`m[ ت=oT '> /)`o 5['glu.˷p} x֝LHֿA:K Dd\xJ7&# _Æ ן9{2;5OuzށsR SugG4]~֎|j.0]: Yv~ʩa tQ1.\ ֠o"Wvc8x.&Tp q`/le,wKd*QrLD>k5JJ *B aRN,UT90P6䒊u'D*lI".j`ElR:&܂zP 0uD GBpPҐñ$րB |@$Q#Ǘ!LV̹ gisz-*,Qd ?!'Jz ^IAC9kt!I1uV~A@D51Ѭd>-  =yC_E [w 6ub DD 7=$ɟ߮{ϭo߷@wu!@4; S=ڴ$QY),9w,&jfl9 W^sQɰТ8+ԕpVVVb& šLL(bR<**F Y&ĒTR*iچk!`"8(+dZp[R' \i/Aa8Zbb=r]ڪj2m, KN_yi M+m,&tCu XϜQ.&p}r7}0F ?P;_g 5QCQ_F٦iȶ\ژ#tF9f$ Qy 4DԘHZ KCx]߆|1 -5 {L3eSz;@mrA|E jzjBPRvᨮY)0s<$O ̴I{51%]OtG*YΖ DK & HDD$"XW XuHI4jMU慔 2 ?p؍*BZ2TCfm @.#TD<<4Lm.$M/*"f-y4NJtRI[P*PΥbU * NW;]l0Qef(ϜTP߼;q-jbkKJ`.鳤 kX8Mf(躎-ӿh3MLNL()ݳCa&@<_U +nS"B3l}Qei&`ԆvD=iCT`ul(hu^i{f\y!JA~O,i/3LX@eJ>m^ttm.cQLFtd@D bW``6Tji,# 6J]A)61flV` P!Sk=>756L1x}Ec@]bХ~@dIoDp1 Brk\\93reqxj'}ĥjs\.c5&/^83j.f*0sԲ3Ѓncx=ɬyuD}hِ |Tp{ԯZ0| C!cҫ!AC&3븶ծ@DԂL҆Da5ْ w +dC&Bo(03V' _ð{n~v2<5Defu,,XU|ˢuܨ"D"SaZ*2T^l;юs#j1;;(CХ"jџ>Dm Be2kN٠Ha>Dys(^XM@b},L66,DE%fF DM$7k1EJQY[h@8T#FHخ؜(^ rkG (H!;}}u6)\&ז`niWeE/:^,تR9~;DE+Fn`%5)XQǯ Tuf+w]f&Ik M% (tl; "}v(؇-9?.S t$>1>u}HXDs:N׮(4; Iu_ EOѨ@i'кOURՇ'-LzP8K ]Y(<!URh2Mwdϊ2Ye=+̾TM g4P &< SLew>VҨM0]3׏Z.?HGuR.L\VTkE᠛S@YCdb6}֪&zֻ52YPZU؞CXYEmF fCbpEhVRp.DZ-ʎ5Ie0^ 2PQf Vp4X7ܽl&DN@4l[V#Pj9Q&KsK)_na0F~&3jtWW^9?A?Q'fWY-hyyVa!~ML+ y߹37 އh:$h .f}_fJꦼ=X#vRzmt6*)䄥j{v58\,^'x  =@T oOϾ:aÜÐӂ&x\ {!ufuACOڱG^vxlgmDi5baow }4AXzvtggߏF.'}$?4ʬw"9Vz" Y]Kөj@t#7ƃ|>P+33Ss֚PWRcoѲ55\)1MA' `{8v4r:.҆a ;s:TW'S5xqD:P^I|\\!) DM*x!6jc!rJh 50\p@ą .\p .\pr<O<OW=ys 'x'>Y ?;EcIENDB`PK&7pL|z??5Pictures/100002010000023B000000D92929027D8B7ECDCF.pngPNG  IHDR;IDATxSG/~;f3\2t0 Ctw !s[ G\@Milqc<m"vKBBD6ٶl 88YVنU?vծZjk O<-/V// @ *ѣG@ /#!lNI޽_z~ _>{NC 0+E @Hq0%KW ~j@5o^@b6AuD  -Dv['=~xſ>wص[,= <_Ÿ}Ϡu +lcjk@Xgs}";͉uNY^NqٵC?aCSk0O$7G{Iʓ)-#;6F a!= ==/ #:,^-~9$Ca>s~i(Go׾ ?~R±B!5@ 3zzz6'~EAFv1./Q4udܸxV`qG{`އߞ|]!3SIMPYĂpIqcإ,ُT7gFϔ+ϫMBO@  ŧx20bj?{8q8qAd~>a#$H1gk1cpp}<wn݃I.a}ѽk\ xw0y_D3~rO~V/@0|Bd9+Wp>p=ŝ=Evph=<8wTdO:g\ BG Uf䱒׭vɖSKv؜iƅ'yQ!\ܾ};CvbzuBANb~p+x ο(Bvdz ;sftj5?vxO?><á?/~< MfށO=>;?;ݟd'\xޗO׍qM_ۡ~~/ w/?ִvNVv @A?6D,+Zts"󯌼 2zV$8"} a=y#i[ˆN8y}?#;?ww48:( yy~vmH Nk54¿|/3/gq?]_^9ii鷰]??`[?~C O6'~#8ZPAmk}c<Y>Za tDYzK]EL햋vz-4)'P6C7`;w!lNEޕaxi<k|Ovuf}+#v2 ߻<^>rs8~]p @ nkx1.ZDx0`g%=/ux} jꠎgK}@ @xN$D4͏ZD P.{v0@ [}}}ZX9M"vX+D P*Eڃ*@ BqMx-yq b|llsV Şi`<I>M8ب[$72P{ xm*8<,//÷~k ?;JX,&llGw i\1`ǏQ\In~AnAv| fT TT"Ⱥn'CW@`W}W+?Mr7;_s\Ņ?"dyNfVd>`MYOrׯ 3:2 ŋdR+VxQ%i6}W+?jT[P&6F?tvvk3rdCDC?[wd?LY;j!˕{jj }]U·gϞq300 F͎bx{~_c؟ ׮]#󪓝B6 i0V+h4߿_'r N`tDvJ$w_W1x<"ioo8sZ* FP\*r}p觇r`1*7͊'s razJ 3{ydctxp+0MeV/&w >=ʭ/_V?8Li7ܥ}~?d?~_ߌrK###Qb'O[!L&n/T0V |+(:sܺuLTgz;[-6rAp^/D'" IOtNgɾfN3r~…rCiV9K^5?sm-PHBr#^/'3ň)k%j.Ϟ Tl& '/XK/>lM*(TZb oLpH$rsp .y-lm6܂~{v }o͖!;_-~U8AŮB%NgYk?א줟5+-{A**sVDv CwŢFp8_(,W* W۠g(^KN]{v|UQL?kG~psrh)B\!9YiGvHvJQ|nAVd!`J|Ww^Iq/9}{6ZRԳ! %7@XJu-*(6W}U'_Ǹ$Ax}}|ѡnv{Y";%臫Wfº[6w*SXQ0'|i?_|K%;!e\3ngB=<_E˕x*ۂ+95_9;h J{hhJOgI$}֕ȽN)c~%7k(_~ŭ①#)ח0&J,o!C"cizEZ,~F;X\8:YͱR4̳k04x1|_ _H|X´7!\r(Z[[5:kEvRin)@z֕ȽUPlf1qxɓ9 L:oq5VN<;9q]C?gNw ˟DvJW\ɐg}Orb |ϲ8KkT_iPyZvJmJ؊v(14M|>;knFrXg-oN!n\ly#Ǎ9Zx'1nav1`cI$ܿ ma68m59K³ \km#SugF, <}s"} ͵0 ^cefg[}**@^YM8J!6?={6Vlq9Z.kkBʪԣ(Jɽdg= AK{;x|-*(TN9֟7HthHnPg$<>zᩗ{` g'#S>޼2|'SJ^z|#QvvouGapEdgdT)N8^{'9Xi0 {rd_ҥK܄Zj]cZGG6 +E DR _Hn+1gSfK ?#!8Lѣ5yrʼ*%7[nTY Cki~3͌pƒ LA; nLZOG<Эz.D7}o@V峦'0׮)\>W4&W;0";ٴdNÇA{ IQ% 4J+&;$fak!;[_)/'3 xM$sl(KyWSDdB?ߦJvdQISgBS:,?"WεF@R!PQ^ZKy~3ɽV ;RB @V_vMMMwNb6Qyg/XNʑȸYf#w$w9|1A8c[H#;/S*d1 l$7]N?~A l~hll&=0>:Ş˩3=?H9$.݀[i0-S6^$7]N?~A l~x ;CpwC+U٩i!o['C D P&P!H)ԧ DvRJijҪ |0>'nwORbaysBrn\)a"&S}Z  !5BJ nOAmYWWg@vؽm46{صS!ZGoߚm.ݡUpkB> Qݮ^ٱm7 L+Caa*sx٣ EOcC!,Ji\A KEV'n$;{;70#N퇪,sLIC`DZ,m)6dYMo1N&#Mn`}XV,>2Y_H(qxu{cMLiu9wpS0ƕ4@1 A,4q4p5x8t1e'S]0>,9K`XL*}T³ش4b4| xHbCf$H|}$L\ONJd; 咝S-v]S`b 8/Ѕ1pOB{si?}!M̌#π•1ITe&=ǘ2[>&ؤ(å=8v/D,7ϭDdG+ěKp{XG6QedX?h;H/U9Y>yVWpܽ[,V&Oxn" 8㆐$9瘂' n|v0t\EH@E<$+1`$8⪘N7~98w<vjWܢs H$?~";cBr<&xC1%ML!,bl~)gFnCױ*ql'PLerH '-j~l_e&;m>' ϱsBsj[p?%_fkLs,#07[goFNvN&i_2'sϱs?o}*l۱C-y)u #;c2sa?dK-p (_)ɎPC ٙV&%.6pJ0 -]is+5+??_לwzEmA~K_hH~LX3p;tݼ &rd ɑ}8eDb 4 b˘CsCxC,s;򐝘2Ϸ 9ۻnCqޮQ UN,0>1 /l 6q>$eNGϷ@Wn޼ !ڠ,N%e;?-[khl"C J@>Mm@ C R"OS";)%i@ B6@xΧ~ @ /#8 (P@4١@ (d@ 2dǽ{`xx~?R)j@ [ ARƹnH&(@ .Ak‚x}bb^x O"!ul ].Ȳ\rRr4/ GoJ f2K _1/Kr>)K 4OANgrŲ!ɫ lTϽڐ! ] ˆ̌JxoiGo¶mT23iuSmۛpV:u`ō_˾qM~cMN<uX.d3 $q怮g`8F"Hxz8X:=q β8kw$hǭwA8 ~$o>YMg>:=+E/*|KBESe?4kɸ]k`${;5{0שG$߯sNi&aoU~Vd֘O7C~]^3 F}die͒#\+Wɸ~ua#{{Jϛ1aqGRWA %";Ev[| I9yQ$ UpH9:/ě'B0V1\`'vqb&CF}| <\edf~;e^=0~q8b^\e2dIQ8HL-8\%B( Yt+6ϪHG8i:n2IT($װke'Jr5sC2gm8'N0|jz<`+:,'X^>ġ&m*C+NO$!pY,=2 ʳjir+% )ծ(p\vE3}x`@qmZ/ڵPlބ䨓415:sKm'@}x994־AQ~ AI1l^O1^o@ly?d zx ,ڪmc\VɎ%?/+oI/w-6lVh2'@̜7 b䭰zG8${]{[oZ^dl{":)L9[uD)k,;c< 1R#;ǡEt\GvkJ3,Mf+VENl`mv2be'tdo>fug**shjl eix.#Vs#5r&|k&ĤXO.,mW4Z&+fJ'/3 OD~$nE咝Y'j=Ɏ&Үɲdgng5?pl?\ƶ'@v Ev,5[iAZL@)9Q?Z^[ƈ|(NY`:ˊ4';N1t4fﱱErjG>͂#~Dv ;vd'83g <̏*0r\U;7^) 3*+D{(ʔN=g6&kBzpH!.<bq(VّNJeo 1ټ:eY< 3+nLڒX$uF1{ 9?*$UB0R9”s;(Il%LV\pwrk䓅$$5٩-I폛dup\*fŧ5֔>s}㪎=*KSO6>>gz8[Z q2ؗ a=+ef";%Xve'yXcٱބ3Ws?YU'd'Mn|T; 2p2\mDAve$Ɖ+ft.uDl-ybR.gsVK哝$ 5eZ$d<RȲTL4nc:sM۲ZYJzP,<UhȲ&Z\ fjly`5u>ʌ(nӄk2ڈv9ש]87'!K";F+3>_>NcI/2$mkn-lzћ0c\ 3p^mQ 3;3TF>;F>DqN8iU}vv\YfU3H'*EvЩ6Jl>#jӥH#yVKrj-sE^A{MNM1:^ܜ˙ dӑcNN. W: r)~M]ɞV.?iC֬]lִB#?Dv{Y -Y|gc]٘d,D_,3$;eoOZ^ H$6q?39y)l~6bqc=?Yv5U4?s\·}pCV]_l.Nc';rɎjUsKF=}i Y9dqhW#A(M줷jzF/?Q^)w噌:^ܳמ`$LrXfҢWɨgHxKb|ZE)Avw~V}I d.'6iDygo1Enύy 6$(k;({dWQ>XDvI?p[JcrBo;ATsZ1ߖ/Rw2>\s>;2߶:~ae ըДSH,HjsK 1j>0jೣZx'l%VYy|-+ezN{p[vvZ޳f& ?jӪ{Nɽ=\AB2GfUkEvlI\> F v;6BN{vrEZkOpЖ%G;pd@&hl:v'lY^r9ƤUg'[K!Oai6~WrȎ2} SsBtG翅}lQzdi`N;geYr){t2pBBl*))MDžNJl1q6!;NV!eȎ諼9m3=>e)&d͐"H&IVcRLH )=fb :wۏq)ϱ2޺KoP~ Pϕ's,W뢫p{,I}lӐ|wN{vf+N /|puhoo/Li)! @ !@Xk(P@ /i(H9W'&& 7|CA (PdN*۷Dx(P@ [5gaaBDQ9x?}#믩U(P@ [x? @_\\,9YfEY-v1w߁"uТT} l ebY K܂:[uBL3ɑ n%/ VIo}_p}!?33÷,/ܳp믫wO_XO>];nO]v<î:H6X{J <^IJn> vYxFإ+ܭHYyVOjhSe@8{n`vs)57`0ڧ@0p&S-9zy-"%)pUg3U@`N'5qfҷ󔳃2Se590MMA[ sHNX@,-Hky, 3-LkxniMɭkCseƾ;i0_o- &u+tW|B@\ ԕXlQxL);HgttpwZ.d%=a;\qaq1&+Pl?-D.x 3Glx@<ʼn{rBvxYֱYxc'}g ̯œ/81ڇDz&!Ɉ]LPSk[Ң4{P>-MQ$hJzEJBs&Ef3D} bul`ת{Ab+Mzgd|;ӝ,{RuwWs597d_58ani\j{ |}yd S%X^C/ZbZ|s scin ZL&;wrrDa`GR02듩[#n8-~w4I2ƛe 0[nM\-@/(-X,Ӣ y2.ř^pKRdLYٙ +n]'O$CxJ+oxzIݧYO0qp1²]7>xϘ2x0{'8#;cdg{6"mVTFxz N* k,@a*|Cwqw)8(p:cIa: x#ނ'4iӨ^5zP9" ';b+5֬!ѽ]%;&hs=M:VM5WWqMf 45l%uCm\ZIt- 01Dz-`Y퀆Ɍ>*;Pn8q21\kHT9|='&mƲD|r+6] JC5l"njLn35XXf9QƝTD%; P*^&;ZK rB 65 ˌFI{#5C;0u+tkN!`n 1jhkkR-‹)OL^[֌}H-ǹXҵqi";tBF'etDFԔ5==]`dgbGGĄ( Vd8';<܁c\\kgN鈑}OB.=$&ٕ҃*ۘYfHnbGvY7)lMh&g`ɧnHL6d)LʼnŦ(Yŗ̅SJ1ٷieQ/mm,5YGzP,<UhȲ&:h3ʝPVi+W]eV Odg3 "KEE$DFrڕ_ƒ)zsZOۯK+cKy~?d'b5t\N17*ȮJFYA4AS*Fv`.Ԝ7avi6 qzj_6$lDv׏qEo>;;pګ'@F3GN- E,•,;z~Bezi<}Yv,o9b+n; 9~D$B넸ĭ(ifm| ̉$'n1^.eɗYŲ%Hǘ9e˙3Nƍ"Vݜ:l,t;e h-9~Uٔe?vQ~]^1 ;2Ɏ+WF9ٌt,acΊl4Hx>|dP![YũS^/3n<t/:>+xjGD愹\dR28Wmى;[$%P>28{0]yv"qN,i,8a1 -z,;qb4V*ƧUd'%M ӷEk1As rI*K7D4*qygo-Z<7'ؐLi u+Aa”]XF~#m,u2*3SzS^Y1,@'5'8_hzΕvVuTh1MdgEd _^ώb[{vvi"Ou4o5zbbmʍە=;zyJ)0vDV~oVɪQsQRޭE NYMg>L{[y\'s]r|%A圏|H^hh0Հ3d<bϨ%o&@}$uiaך!so5,Nil!'hTƾhWchdFB256ʾ7 /re6or&}9]-YDv';#I*A(q8`۹ŧ$UpHHEN(N 2sf'l;p&`bb:8Ѹ81?|&M@L% Â/#RP^ݷO4ω^$bgn@X)< 'w2٫`&f 92ϐ,])yIY:WÕ) =Iq,Vw8Z:!Vxr:+'~?[$` &OMM28mg9azj2Ϧ8W~'{,uY4bЉjW,S20[)dB 5(&BvYu : QnMDYN>NkA1lִa^'{آ:A4ck[YYhz2tϭ(QE+)GHf3k`_+jIZ&N`#iKP(ʛWT䭰zGD `JGVf0'Ώ8ב*aʬpEΰVa9Ev$x@c]GWs2B7.e}7x*@0)̲qhvF5qQh6ב4kJ3Mfg\66;12cVȎ 5Ϭf0l[f,$=|(MC(p!Dʙ U͛fcY6>fpE,MjraI;0m&.XuS ɎM7yZY>nXD5@OrLdgcy}(kIt mՎt#| ^1gYeNe`ӐBzT#H<YhnW#^6fW<0b P?#&u%v,8C"cΊ_|:_~%>=~-?w8÷RrL1ˏ< K{+`pTaHo]pf;, ["+8&<ݍؽ'!Ir%:n٪G1R% tڬM롧,[=|IlŚF$G\_Nv)D8Ѳ(5̭%ބWHL~]lsd9hE%]?p%3auo՚?;W5}~U8ר,Z7=l٘!7^{n?בV6mDv6$'Ѽ fnkuk^!X\dzEiO6MIAvdIq!uXDv";HtȬ# +L #|[wsBw^8p`/m&}l VE&'hᰁ3PaՐ 8H>d`TJD]\!CI>ڭL),RL&!|-8nd'KȎ=Vj5)`#'`B%&WGl:r6g[7&mI^ V~}:+Q5h| dMiϨ@ jtV<9m{;sB@Zaɳ(bȊOoܿc.8;EW+W>2##Rl1L0D FU_fN:Gv^/wRFƚRp ()Nax"Fm_ʲ(=xc/d7|>5 ;3Kݞ>C$.N3J# ^lh&gfBlb'nRgq!MQF\UYUfIɾy0~Ye'>Ch~ pd?Z&_KXx<'ʬ:lYX2./f}`lW}D=KO6X yY3^5n OrV17aVxsXދr Qg>;3pLM-٩ʲ<ÙS\'iYv';耗𕏭}:6Bvd7wBb5(~2!;Ԉ3ZIN뷽,ԇ 1IBӯO\k6lvrI2XD)䫛Ҟ1ώt>mcghWE%mgc-$c?vmuIWHȆNc.ȞVRI| ź[oȬv(:쬈aNj#I%;A' [ Hs\7|pÐ_˭;ik곣95>!itƇyOc咝3ӾwYq"N+wDGWaw^$<\8VvPRNi3b;kw)2|tufyydGVN%){i%ѐPG KQ7VmIoAp_<1ƅIF>,h~Vx0e)N*IViѓd_9 OcI~YO!ȎoֲsH撝> x+AE6bOy^c릤g,hV)Ml=5JdOIїmD(pAZ7#%;%[fQOba\`+9߮`H铏4ܪY#{)>;\zuɐuf~\{vv׉Xsѓ=;zyb)yp\(+pu\:~N9qqR9`'lUHUɌu \}׈PzF$ۨYeGrlrjޙaQJȎ31++)SQ}y4Ƣl%$8h˒8W46jx6;={SLz\qɠPݔz|ū!;HP(Rtk 0x(:>uR0 {vebzec|k!:̼SR!}eA_|D q&B ɎMoPI󛻲%LyRp[dp^j5)U΄x_\=vD"bNYbe%3^Is<z0- «W a |Sס 0 "+!;@ Dv@ ֚ (P@K*Bv~ߧ/7(nKFaqqu(P@ [O1o}-| {_$7<бt (P@MEv" 0_-< b3s2<>O(3 (P@aKܺB NLƗ aH hE*_^PR؄aT} -㆛K e.Xr;*rVЏflIF^B82D&G_x%bܲmbdgZN{E|ux= ֱYH`O, 9Zu jM& hoTZXdG|b1k j; RI:ssj<٭3TJw2Se5tO󦦠ZSRlUrvmЦc4tiz5G<|4@C`6+j۵ &`875fE@.[ y&J48,"<@Ivd\a9"WZϥ%X|ɉǏ 1xW OXXٱYg22y6@$k4pXXϙe|-SQ[>h 8J8qF`yi3Wx|I`05jN>&VItℹeX pY24/-Xn LBz:UE鮁 ]| 3]&3"&41sKm' X' ѓe>yRq@bc+VtwKıKn.I+;3$;Y#VGȐe eú71[0Yz0n!2Kw QL./v|EU$I~jEɎ7311Oq#;٩Ry+S-?O_W,>)rpL[&4\'4Ǟ-u *-}WSd˪-"%-Ꝍ1ݐAkz!T?P[ mE i#Hm`͐6,YT1,j!cikX[KiJw: HRE_ hT<$9aY!9H YvNeb[\|Cv|u} NN8i| Iv~48jw 6aeGћ՗1CGwnkQyg Fly#f>oɭqQC)ZVs#-Ml6s4u N=rjeT|6cVp p B ƵnSb3GQ=.˂b/ƳȎ.|o71 w:aak"T6 x qnvyxǬ:쳥"\yq}zH׏p\dǂqH\;jROգ9m]^?9wFa ͚8%pBE3g3CmC5IILBe}K!mDv[ӈK?dΒh4 @>A (+CLܵ zķ×ՒSvmmZ1څw6sx-X#$m+MɎD{ #-h,m";B8lDc0zNGv8y[ 8m|]ٱIAv܊Y%9tdg6CvXʶՋ*\$\}-G|ڍ'On^EԤWCvܤlw&%ŷ${+<>; 9~Dz!ޱxs*OPD 'ܭ#;Ɩ%pv[;,i3GJ<+S]aR ouZ9hMd4ėum1rvxZ-`LC3c aE&^SXhCf'e  9ގu|gJ'j%_;'?)< k?9|%d~lpCV0VXfCfz<>gv`sXac[ |w* L&6 6jy=u29li;8 G.suic:=:iMeu ԎE=p'^^'v_sY¼eF䫄N:0;f. ]b18 Ҽi  mđGEs}&y3Vr=Esĵ5I툳u}vZrv?TnI!Aᦰx Ȍyk,$?b>ڊf{{ЗY5n̻cvQtn=,hډV6gʍv=z wRa^x,L khm4??3o~5m7:oB mk;kl.沇ݍY7} vB> S밣7^w6ʪr[a K n@XkYwb;; |_&*S:fgg'? ,,,ףc8??Eg-5oO ".Gmk gv밸 kkkӟW)221pu]%Dg$4_!!Gb;B"D!"D!Bok*TPB T ~_~N *TPBiCN/34[,x *TPwP6+>B.L&#TPB `do t0<Us X'rW?wBWj\jcOҏViLy2 kk+cޡy/έ7Eڡ (u}Y)?|&Ԭ!H2$ǖr=6s"R~~) gCHeQH睜MP̓||l1-S?z l0> w|HLƧ`;+|"ǢY[-7~8ەNHPig`<|:<큕[;_kEٟ7YC]\n"f51|aߡwMn4oLO^Ew _e-Kfn:}?>,g -k*L"!~8&-;I&BBr;01B'P@02I> \;T' G214UvQ̦`gfS?<`iC^1io^}suէщR0 8?8I>ŦIZdSh=D3cQ/ eMAv7j& &QT@*x*W%r,Tw'EpW  B0 P:T,+9)eLx:H B^U(>8R5$ܠkg5QUa73P)s 8Q`Z|OB?JAq;!ck'r| gR}!7+Z:2x?>\}إho[\ 16"EtvhF&',:,R`4;#]L)ZeSR&3t Ny[F—#/@6E&G9p a<>A0l&Gs7.u2jcC(;wqH, 1 LN'v!$Ul*;V`k)QI69x,UI!<=  v.Q/x™m.!Ƃ.Ћq1,#p6AکFU'0 `a*Jd hZu(JqسLň۪rWN8' 38l-; 5Ig2?%i|J&Q2;1v]$RQ6v28,)2(DS9Heȏu$'H;'gC<6D&Aru3jÓ%`bx*_7ğ)H;v a88`ш~ͤQXh Qh̻ĠXAB3&)RKgiWW[oZD7BDk{6EjSux^1 4s3=,a3;vF|LjIso1 <,pl욳  / 2&y911(5WIi-}1`vc`X`3ն3 v}sCtObR^ ҷ^9p R !x dm[2wzgr<Вh#r*U#^Ĭra "TAAO.Ռ228%*)alg.iUIAa&Lf AZ|\lubtˉs,uH5bovH{lF28K%ncv{bOb2wm;f2jUv.`k;ψٱVcm5}}9G@00y9r2CpbcWzXlXNU3Yٸi+9;Cc @,d4 t'Dw;F4{i' rsOS=ү=Xұ2m <[o3PB%re4O ǯm?*ݞX- a23̷cb$br\hiہ׊R4MŽ09v;|j`*y;mdy: 69;t.S0mJj8p'R>;;sXmMrٱى `Ώl,7 4`&S&[zƮSyG>=MW=fT$׮ Zv]O2G}" +# m1B#߶g(e;m\`1wnZ)vsDcVC"MsG"0hOB"F9W˶!ۙ9;|rCk J 1)RS|ZjT{{"c>1i >I+,oFO1B!㸏_+ ۓQQm̦ 6o5eˋf-e0tюP<ﰍGx)찏wwPvӮ`3֫ lN>ۊgfv &z~{Llj?tTI3qZqr'z?ӳAY3{+zªZxsM{ ;rOUb`t׃cϛ!\,L\<;VbB *#W| ?zxA=2$vv`g'vg<*T;B *TP*TPB~`"D!B>S`y65rN/ A !B"D;j5yۿ[x"D!wP6^;7N# :>>?"D!B.ycA7 eQ)Jx޼y#LVW [Px)YF1 uzoܖ~?ݣ!wWu N4렜(bl|j`M(ȡ~qqa- xoMܻlz )lecLT/" +֥Y>Rjew_c/jeQ}.zUA|D2[%{Qirl&~>xfeV|יu8 @ZLn<m؎yaDZc[%Y8u$nN`mVwUw tGm1]Q}kv HΓ󧷠.FGw^awN7Uk}O;#9>Twz+i:ԺX`iex[`A6Q*I~Κ&iew FpE`R3EⓩJܯVErop.q}I !xI9p1gK[;N' .p}Y¶L[hCzWvM&!bLK7a*E'c|1YurkHnoɮѽݍL|y# Y{ɟxov`n#d9ع? a:܃~?ێe/! lܻ^N{[,ҋUl{X׳&zu/">XM OsD p/NFkv^5a!,,6eP1EĹ4q 0vH @ Tievmt1B&0qd%^,-WI4C^2(5ll(n5I80y ڵ@jkmam نZyϦ=09Iӻ9C6/9B-O5>Zi uFouX6wh7+L:q|k6_1&ة+05H\u;g1ʘ8Tiط-XϬKs+V;|pd0^}Ӱh݃t`y/@(`838ggT(CY`wy+١ewrZ^n 0z[h-3( ?"- W<~/8KDQ(| .chk6;}c{O"ؽGd٘qLl5*_zu]LU&ro Gl 1r񲶗6|Ȗ:N>Ik 8ZF/4Ida xhI<$`},(F*~T"ϨvS~rDp4{fs-e`1l܊_- wRl]ayKXO .JYiYȼ.D=$m)kcV}0Gp wI݅]yap%+IA o3^|֡%rYM9b3Eta{'O y&bW+\U`h_Sȋ౑9qߙ;%;Fm}aak#lm^ٵ8zRmě$^/~9 2ח4 AGN +t`4&ʵ '-FgC3n,-e-ӊϠqM_N:7K&+U7lzG;3ݘwx۴C1d?;-d }[B&Q׏\UGV鸡+4]ayg,Cin(&k,iXᘳÄF`LI[̱X5ke5Q퐅iU-1cz i2W&`~,2@Aj<۷E<4Dcy[udvY`I ˭' p͒8Kmq%Ka>)vXj0VF'.0 ,B pm7L9ubl\>9UQٹԿj(^N̤-js >hp61;[ζzaC:q3n LR%;uhjFN-`lZKqJnnVCN{wm9;uck&yb:8i5gքs3_^;cyX흙-cυy 2WcqwOޖيoHa9M*6]svtX4PLps/yvXYgf 27m8Ai]^@|2ovdzNvJ|aεIIw$[Y[Y`y~k4I,_fJukn8;s [ph\ P0~?MovX5YZg^A¸T`kep-رrr -zfj-#~PSv58K;1;xͥm,nɔ1oO];)5ְv,3_v;>;~F̎kI X+D0/u5VC!c1b7ͫN1뛙v^_,#jk v(C٨G!RE<K{5J8xfK6HzMEZՆg% 8P3pXJRiZ`I|Ivjt5V 6W0NM=B`z@I'ph;ӌz?/a 3,Xt<}`%?~rx:z JPa} +:6f|/'p;$cw=;mvXEyDLױGn`ľ~N\_\IDAT}_<қ69;W4 x)MQHُ)XPB̑nc}<ٶ?3N%ggМ#xana, ޾}뾞?BOP0;=i Sq]1cEL 뛟i1JOlu`1wnZ&i:v=hZYv&DhjǢ8XYe//0AZfmsUۊ݂lv8(w2)PZIiuP:}v9TZV r슳O ā +V2ʢU+8JدhZ[QUl>AM9Ț_yfڻ%m9ڱ) .y;Ts]? )l6".Ka{+1j!B)27XsBqix>B"!B"D;B"D! y5*TPB~Ac&J/K4CV/DPB z:ؕͷ~ կ*TPBmC\.CR+;\.tN/FPB zg!Lo~bCX$a;P@=?s{u:ixŕ4t {U}ȵS~UZw/O+dhzT|6` euZuRCY x' MABfuc: IP]tyRzyg|{MH]8hjcsU؜u;2 KV)H!?/_hSϖ&W=D7Cwjf[i"ףW`;q?d5V ;0^S!b#c;Av>cѬZo&mq+Ms;! Tn?53߳l+pM!n<΅vńyQ(L ıOm ryd{a@S.kjW"|Li&3_-q${;m1G7.cZjcNO2x1Dn0V &|6 xP X̺SC00od60Owra6ò5ThmU%.9w.rbӀh$nd$l tAјw|Od`hIV̈́$1*rà C IJP%FULD#_; _O0JUŶb>]ʹDAZojtM(>8g4$~ΪZ,L\!`(qBk;, WjW'ke/\2I7ФdRUZeP&Smlbo2M:cQ@3`kB(C=Oj4ee;\ZS絧s% L'ZXB" ѤBl rKcgp\2H# 71\)#qI۱ <㎉O+K0経oTM/nㅹ9&415H"$O@ l-'^k,r XZm،x `7rUN]F/4:IȽ-D'BTM(fD=Tx*gTZZgv6hnϵ}vŸxBo줉WMǖkdL7NXkp ߫qֿt튷ыQ>N5N'Bmw B9 ':;#ybO=Q=R&;2q< yL[T-S ]ƴ;7;~;LJX4#.Kvy2.Ð(ǘz|a"ӑP/ ::@@Bl0L.mAZv{0fegv!HO@D$s'd]p|rg!‘gm> ;I<pujvTί>4hď^栐 /N@AAxM1!e sC&tѮpͦIB7jtlI (\,BkqcsXz|nY54{v`aj;yߦ CV.B"tj`GcexCq(yy1d9ˋ1-N_` l4_hRC-508 #0:2I&w8䨰Sg:z`vHN]gLsdF`5uSmg`!wW6ߓ6 XncKΐ, َ!gt"%R,a&nw%d+4X?1 ēǦ,@A_#XUh2D^"Q{h0W*}_桜\u)e켆jq(@B@^%Y쌞drZة$ܓ C?t&i)!B d|{td L#qd %^vvf^0 E1J"Y9A'˥ !z`?1I0ɉ΢!^$,-Yeh֜< @@ _]jsק,e[JBj5+{N,7g@ :;0;]v٨\%٦mQRox!O4G(d 5,, 6 4LGw4:W僌_UNهFofGJa& "M?Kcs1ڀ]f) 3A3HH 0a4 ŽPKq}-y4Sfޠ6ddҧG'3%-9;<Þg=Og?peD@f̚zl9F&gm QvX.M ̍Keu"wovsDcVD{v̹m{y 'V!jG,Y+Ax3svA n.`w.@mj%so>qK8uܲR僘$>;mr)+A]ɸ|&7>X5l*H?DH@@@@@@`!FXc~~a ൞aϟ=\ԏЧOxXn%b6%|po}DRԑI "$<{8CN5B8L!/ɵwq/qN;БC.Y.wooӜzڞɻCL|oҌ΢YַI{?,q2{'~ ̮ݞп}XN|(.#BOQr- U7`'9)!Dg~wʤ94dR44h>^ߧ-W|܈_2~G>$1b; |57_zoÙkc#);3?ޡk  j*^ pwOPr1("i<㡿π_%i!'~2hy2߰\{ ; i];?P5E=FAĖM^,s(qq||OI._ql-7ccFD?xNa@_[uG7-'#c; ?#"aߠ/./B#ce5)kcO,ʲ*y:~_el)+-ֵ#cFVB$bmnJ^?5C?&xoɤ ~XI'2FTj{2Y~?>՟߾}7X<',^|61"H\\~t#16G(Ji^,ˈ4!_$D kAY;DSPDUzEW븋Çx8(e/_"Blνh/o~㛨O߿<|ߍn9tdk{ђNz!xeY3>;Nν1W#b!B~u ;rkŢ1A}2>2agP&Bdw[fwЀ{ !0t]{|O5 MQBT !4XiӛWd$MBl$-L/?$%VO^~h'_&Dcn_8-uK΃A&{#i~`7?BƓ$~nCx`3i{~sC/cgi}Q,='UY6}8ss<=*p>{KPK>!|gIWEa A~_##c>Zj"T "$@ǃgFsŁl?6?\k] NĎǫ=o2-P@@@@ EPχ$k MoL:ȃ BEQDPd-x"$ q( "|(Jh <ظD((_u'=Z+ŔoUb5!BC &@KX[LEE_d~" r}|S8F! y055yRe}JX,$6|tVXhEED?APA~_~%a%Dhitttٟn'?FVW}ܢ |4>q}/ԏG+Bs^$"m3zx`Q(hɩ5)K_[{!|AGA"DO}V+IV"Ȋ=뾫iV}sLk$"B <6=zZWxQ%#ij,V{dd/RBx)#A?ʽZ|T}^+Oӭ- BkŰVn(°}S^iD_p {uA62 ExNchhoo0x<4D(A N_̖&3ٜ͙3ƾbV^9Zo~h"pjeq4 MV]Y `)>|ȮrK꜖%)zE~{NLPt4zkկ~M19AXo68dzgف >ˁM (Iv 69^ tOykZߋ##B/\GHL$iZZss?)Hld~^ȵ@+7cDiihڥ*>y;#B3r!Fl8<+> {X} Zx=^Fd^drǏg繕6`?_Xje)r:l} EϾ( +]BgǴ*ik5 =Ǣ_rSݻw1^zrg"Tl\*Ԩ4^ƥ7>3I<0g\fDo[NwCKfffty S᱔(d[(BJW(}DгiHp=w[m;+7Lsn§$n~# ^M{g/D-64g4ra/;?ɲ>GTx,5 J USB $]Iĭ[+W_eBiX5=G&~"B]R`Rj4vr]Sb;s@޿ً. ;Ӊ(h Eg M!J`wFHS>+5vWqygϊ>H!%ȳy!l)eg(u}1Dha#!];Ái=k1(Gg1a[Mwi(Ph޳^ 9>AKEcccL@7,ջ.|_{-a``9DSsnv fa>d1=;#"vJbdv^\{Y"FI5/ BE1 k+t=y~.uVR#T]l ժܴZZyjmߕFg-KIFӬrZt]nJfZ,׻ףFU~r.GʶSPz3u$yBzG4½q&B}hnBd O'B7#Vh侾<~|$Oo~#3s͞b7}w7,%#吞bXwc Ni!GӖ"BԬe5}jM?kKb/Vn *>_@[r&Zr("zU93(zE.&0[k{- z.%>TVϔ Q[oO|rاd~ڇ1BFFA>7߼ȓ>!?Cl:쾐#vBD,|@O>M1AI((('p:!^ <,6hEAS]BHP5|2S[4-GG.e'MS4_A~Fז_~b‹j}޻5ϔ,8fxxx.ZLR'_S=KQRRZ_{O>ad/nt(yǃ,"F4n }M}cqW>$jiR``c["BKBDh9EԿl"/V4SSS+] UjAR0pEJw0D!g4 MKY6~rr/7~" (H(qrH$;b<AVzUI6mEU ]6ӾmW.ܢ l/J "$F hAD% hA}Z@ BB` >- @!!^$b~1{~);pf/‰r%DZ}: #CR^yޯ&r%Kb< zcfR\.rdoXTXdLY}'[.yoV& 9%s [GM&\OqV>[!ĕ[+p s~]3^gzu{$v9;;&brfmrrc]%$BAm0A6ڌ?A?7ϡ=K%F.܊-bBLTqP?Gn7|Wƕ]Vv~B.G"I]ɴzb YS+[q̈́ Szkf &׿e(ѕ~D;O $ w`}LDtML[8jǞ =hJ>Xqu .iZZu2+]Vȵ {*s;/i}0;߷)KL73c.=%:2=hu2+#plݒIvB lۮŕ#뻎䞭 .#'I_͚cnB0]gֳܷ+*uVh\8+ǷBoP"4BcAA).2gz9Αz߃{乴\h"SPc;r [1!)c~^ژa]h'BM'rpJ8z0$m}&D , mmmַ~dh+11$d q۷d!9!H:` նx/3M+#, ?;[;PA[I`z"hI1bwD  E}}.ac?fv|z3+!eT#pW69Bʝ [gȄ 0 bp<( B LI[E[^cDI?qr+I M{6H+#R[gX>ٔB;BƛcFʟ>l=ML~.i,HI-:r v1gvٟ5םۚuKͦsYm3\KɈIUd7&:rstl`mb;!*@ND 7uKb<.;:ߗU!6q>.6$el&B\DhL4XIn/4iuh$2WWկ~HN[|?AFt.ܤX4ĸH&BTRq߷MP"D P:FX$H/8M ,fisЭ<"+@b=~7o#㲡U!x/lwX r󗩹7ocN5Wn޼W8!`Ar,0HJx1Ve)udDH Bre&\LUnTC"܏$B1>vԫ3ؾZ $P>&&&t})V7 iG;;1~IXyoMvVsn2_z/ˎ1"48 yΈly'pi+:tGW+Ȍ|ؤő=GDIXthf Bb<.]fvȎ^wm6FŽ"tn{6.{.0ԩM@j]SFWlNJb;'N&"FX+}'mKo RuVb< D(LkgTD(wݣ(][]~ŔmޓUi^P3P 1*DS5.qPv>X,pY?KnwMHI>W{,.ʃ?SOKigb-^K'8X"$  K@@iK@@i % hAXKD裏>0P!DADAADAD?_QPzӉ~wp `zzz# D Jxz Rh$#B@DjfffT=#D %IP/&%A4PәF"(3)cG!Sai%-=^Lgu%N;.!@*'4UvКéRb TBb?ĘҊO&ǑH-$Ԣ˲ZHJ я-JRų&$BnX!QѢi  `ǙvRfPOmތilۏu ;qš|aif=T(2`HYl'hFXqx>*O%ӊz%GL3]Īm/!2U[uO|0='?.F53t9(z=j'! FQrMD9I6Tg;ZnO<uq:t ř=R)\Nђ+Z\$&ҪHFQ^]-aaPQt}To@xLz?ɰ8]mŠ#>F̾l_riMڜrjp*Wieiכ!#KiX::߭-"(6=ܱ=Xmwq=X, X8PI&ӝH"}E#,l a?Od ISq¦J~.0>5o''WNjj5Sh"u4)d~ZG~X'#UuɤyzyJ8b^T$ ᰛK{ fSLъx2dRX |2QV ^N[h^3'%ɪ93%EG'naB>XYid<@(D Ow]v>'j-!/7>Iw mW琥^ݷ6x}D}cC?ۍp8yf$D"*Mp&8DhP*-7_|6ylLa(2bv0"HN'd>MKh8"y:̵I+XawEe-Pỳ\SU8lWS֜ThhC\-3Z` gȋnF+s΀mUIiSU MX1FM*\re'5/jeIf-ݧBhԂTl"N\{qB.a\t +OeE/]:mr :{TEj"Ll>1C_NzyhH9C!Dz%`5֢;]!zehHQkʎT~i!!G ae"٠7"$)g*&/& e XMrn;hV%'Bz J2p{ULB'B5n<sInAB0{K4{uσ$:IipfwG}=ijY(HuR%D ͵ʶL+wi,K3Z\#!P\Cis5%}y_#} ZY[Z{kaVቧ4*|cVS- }Aram0Bؼ~{_ѭ50.`&p#*&!T*&D(1i4Q < ;Yz^N.Y]hχZ1q~3'mY1utfޕ!BԨj! .u)9د nxsiGUPb Ӹu2V*χ*FmBUT;bT|/;!" M@PRj$ Tﲦ!}N7i"W R`!/;3G,H*PW]tJ–iE,v 7ux}Ra 3Z{N`ޗ Ð!Z 5ZCv網f0{UU[ٯ<ƪl \w()dMd"DLxX)6Щ&Bჯ:;Rd%% eR9"B:C׬)YLYBII;sI"DV<_w\ХX=:H%볚'Yl2<'CN'YRA6]JC'26vip4k\H , '* #ओv*̉Oh ,r!"t%~YLRVdFCaEϙ߭g3E}kh " ckkû8sxwp{ HLNE ~G |R!kDh51UW:J"B);)K$W$z'*ՔN\yI(Uf Ldl-D# a{D;31wfMceU|#NW9 ts3 %6\a6GVs\LSg1VvI64 dsB8MMc:sA"uȠڬA^l8iv wp@ jRͤRk<̟G?I2"vUaO;ʖ<6hmP]hɓul/2gʍx\ĉJkjP*Qmm2ٗ;)'Bzʚ 4PjHrD'!-b7 ""B{?ƎS8QjWzx>$BN8mVO:fVR>B !ĤMň-gr?-Og?l>[}il,t¦K% ,P4ޫUy>BU>BtTZ%oNYU,XI9:Skي,zJU Byw=@G7Q[P-j'$i'ke6iyH*ij;&\S!Gb;l QLbIwVCWGh/A՜ku;)8NjzT>@i"O-ٱ?KVGH&}o%?Wv8|6Vh8iQ;tDLKkŠLCȚXn 㚫T2d2%5߂M6_3׾?Wҟ?'fj+Iܿ.kE9H1-ve {rlGJ U^?/A8 P~.N2D(6Ü>/szi2>Bή%'XY{bENaz~U-QQI!U9o+5FɆkLN*w>NSS8Z' ]~@ Q%yhEPBPH`BBb=F(m֪p%8J(|ᒊڹO_8/gawfQTIGƤL5ڻj8DH ;*rL}H4΃Fx\Bo&n*]c8 $IJ 59QE~QY~u]eQO7׆PiK-eŃnJv,-&1{FDĈ=``]͞D1f5˼X"TP%ߣ sR+%j2d9P[m62 ?/¿v}ط'W/46ۥc9Jb215m?GȐobbZ97ya؁;.RDea ɁC۲:Tr;vNe st쪣1öIqG^'*T#T'krӹiP#eM;T{F Bw2JzYE Vu 0#'Jjl&}!yDNCmR/#" ",//@DiΈ?*" "lDӧX "D@333*sO<Mh%DAD$Bsssy" A*/!51vwϖ0gqܾ~g+SsJkO<\93eg鈙ygʏ$ed fzkӝGP^OT#dohYkiدާ;d&61tCG?X\W0{}zueQk>DI=ދz "ݿҪhC]U5мr^Nj &ac},69'opR]h~א; Er.m|Q:6aHYVx6z f൒iX270#:"Bba MᅬރvCoo/FGGh0!5nPg:Y"-#CF 5#3{8F3:&dΗ&8t01^׾__s&&sai3;!C׻<1wgz|8[{ d3[ʼn\?pj7זrπ ړݧcB&Y{eD#h"lqDd5n7^ȘtSh$`8pkN h)u { Y74ȃcP&"#{ѨPh5ɞx$a:e4 'B/#$/n`ؽ[x"Wȳ`:Sxy+Χ2k4+5Y5Ϭkp m9y 7BU#؛.Wv_ o eGH7n;#%FjXp>_ӈ4,5rE 5w]Q 9џb6YI( kWNb3~~deO'Bng{yϲdLp֘W7j*?aI;)v lBEF,V]e: vkD.Ej*K&am0Bؼ~{_tk}a-{&/m#OTf/MC٭ st„e(HhZXGYPakBdm|$/3$3.)y܏\ɽ7Č^Ei'Bq4Y?d' #3Bg=H}SҞ7!Y;3ⷳ4绂*ޯ8ccU6g2 7mgeWϓhĜSF(-H[t }<("Ӧ^7+5EBذJD#d<U5:йn "9WP/8~;@==51}I59,X#4yL?SӘ+`f4Y P=+ڦAVI6$Ak7{\ ԤH;\IȳfIN86b0([vDhMԟ+vD[7*+qyS< I?}LˉJk '.ٙO/t l=eMŅe5^&BZIJ B*rscJ?5xǁ!_I",r ]Ilaɉ\?Jiw0Q{"d(@"3vˎLK7R9@HqA7י M{|=~LrL1"ϊr4B!J-Fo'iiX\KϏ¸j~vVD(CnoMկk_}+xO_o\3$Bs>5wQ4+(ծ1 {v))lGr@ 4KO$Bꀾ_􌐯lM 2qG~d#wAvD;,}FFTPYDɚ2ӝn_YE"4Z(Rh0ˡ8f5[#7ɎC|g(F\('``Dw>1MCo`DAD]c~kFШ|Igōڻj8DhZbPS5e<''FӘQn pt:$gIqQH3 o;ؑ"TEX"4+qV,{/w0B eیH`j\;l{ڷhUklT4Q) :|i.iC*bPA&0BEVjk,Uo\!% ?/¿v}ط'W/469ݸIysMP̤#;+Pgݍi00"2\s9DvjL;9I(A 0>pey!cZ$4U䕗LO%OtLG%RF9hS2Q͕ʝ,#'Mp2fM,5x蹿| iDsړ9市AiVK*Tz46*gao<[\vjwd^fQ7,Ŵ?w͎X89H֢6R]cɿ&{P)GysFc$kf@lM6)ރ\:}&PfvF#NUoEd[ ,ivzŸVjX0ښroJ+>@<G">δC 2zmflNc~Y2{a'N8Tdqzi=c@*CƋ[ =-)ȁjF7>5" -yl c;r1b1+*; iG6ȠI`8z5fO~\jg=sRQt8zOBrq7 C3Ȫ:{jt{Ӥӡۧ^H,^]LYrZ툖$_ t2^XL%<+*ڔ߆ _G^gfRkȵD󞛄6#!&E9pF}Ϊsn}Ĭo5|9F0%Mڜ|t5F8Ée+Zϴ4ErP2 dL5"(6=ܱ=Xmwq=X, k&ӝH"F&:F(\u ^^ f~6yo;ѓFTi潃K6Oy{uxX3Dd)P?.NlK'Ub o EPW%U8.UTDhA6X7 ¸ <][p]&dNH;WZ.d5QOͥIn`#Rc’M":OmL MՌ|8ٔ!Bq4SAm"L!t8yXdtQبXnPWrfHOMB19ahOGIp>NkarBFգ BGR%{3'(xBv=)6! TC zwf)ɉq CzuߒxŃh4ωJ׶ n7wfI*D2U-MpM' U|%|:lLeO k%lLd(2bv0HN'd>&Tq"D>Tu 2qw+J2[ w!Si {l um38rN8Un;x?CB=U褄t"4ka8Qw9w߿m'te`L)ٯǀmUIE=A4$vZ=YjsΤ*M04 i [/Zt+ĀY] I3jM u/7d10#5LD8F:rBl2&1cvYVS&34-E9 $N;jHhҸMdy>:jsk*,_ "XxL3~J5 VYfVHMu9Z\"dbDY9pҴ<\aLU^Y(0d2 :5d6WSؗg*SH_/ax`2ڹj04*|2VS-Ӌ S/ P.V…j'\JIr=@A"@i]=ulr5CWe ”A%JQp+C8ER* jj$!B2;Ie`*AlL33nNfV; ׊;NxfIJ:y"Tɠ*XP =0 ³3Mi!y0AHF4O-,h! >gbCH,k_QVT*IQJ)lLHu6(vcLk *bhl'm*Vً Yy1v:I<}ũCHn(3 y֐f|񹭬} fϲNXYs5\Qur5G3]-J/jLh'f;B>Lܬ:c7 ^X`G/At%R!i|ׄk=f]"\]h݋s4mqc2V)|d6W)BraX t2,sMaؤMcS9%<S׸*Șe'BDM k{R!F}N u,X>k DLxX)6Щ&BO&W_GuvN9oKƩ J,2J~d!KK'“2X+@v#[L:L mUih}Ր fɸ- "xPL%"Gkओv㪨62'> 5|NȅPZfYbC3 DIYY IșHfK5 ȴB0grD~fwWт K#ay ~mx~{]~ɩPH;:qm> )L"4:WK8Jk mUؒlpb%q9#89G)[#4o#"#0{( G(罥BeSy";]4Ё4psaZͅv1MOI"BQ&J }a9bxlNgDaJ2hփ5Yْq 3AjHPAM꾙C=?$J-W&_j`T3䉤qδ R BGF\nߨۜt 5vTrRg11gʍfwV. _F&Ɖf+[]DOkJ4T9#:iKI!1vS%֟‰wPkw[Gɐ$w^nƴI07MO'(Qah7!BYĴOJTP벘g#TqfMwBWs4Bu@o 050߆rpU;W3+y-AU#T緤&B\8L4-8_*ij;&\S!Gb;>A5P8f'(@p@}>ftJ*gHTnG%/}~;o#$(mבh%|見esvwev/*"b!Ӑ&;7qU*D2DHIoMկk_}+xO_o\3$BScrzט | ]c.ZNWˤr1xَ\6/<~82^mƱƧ髅AI BE=5;*p+]cٮ+eۏm/)TilJB$I)_bdz>S#TktzUv0_>BY!k-R)4uuϭJj;|gI @(΅R* >)_|Q p!z_ '75֬ 4j"ZmnHtט$D!)d!rL}H4΋Fx\B(\}y<>_DjӬN1sV_ߡOX<#lb+p2!ǪPYU * 1|[,QZ!K8f8;R`-LTI} G3]D(7=GhU0ժͿ?o~17'fW`۶Sݠ+pEG&Ye-Q.qbi~?_֢ihZ^0G ?&yViɄи"R
  • ?0tMfUN l6+q琿&`b?|?db_V}ϼW躋9KDn#Zم<>*?b6 LTȹbk Ftj2dE0< p YrQsF!/[ggz3vNM=Ԯs`G4z\wߗb6^1&@?cjۅ>x={N,# BD !N |P996 d&Nsw#L3 2>,WK0yf[Y@B[L.ͷiTɹKS&P O,@ 4_Cp 0$8Àsu^3ɕ/;}ǡ,5-qЭ:DB[F踵WAÓK*у Zۃ 3U rp.j[Q?G?m5pu{GLd g=P<||j*W\e:+UJpyz A( A|n9M7@U :_UZeY0̮фbc@ވKZ b2Hqȓ"eϵ 8A2\k`.X9gQD0zԱ^KkjTh1Cem=xwh b| v/g03D* OGup$PE9}F5Gљ!Jk.?. EeB~j-Tq9!XQJؕ~*U&dB 9hU [#limQ{$^gTeBTlin",Cc wJzT}QiA:|h1GgX>~gZ8أ xp/BH|?mBO1i=~#y3ju \{ۍjA !Ԯ8Eo@t@<}\rnFsW!bfm8a=V(.M4- oQa@FO7Pmw * X&bb&GNZ.R| @W uI]1D0aPz&(ԈV{LKoQl&i#7jY3VMCe#N x !SinAB(܍Ai P^1!Ca Gz*!vNJ|ͤ"ՙ5E|nUaxn!05c奒p@A% !n2 'TDك=65RCYЩʱIa},0 t=Q9:4h}CP᧟z[QZZ.c14p;%2i'L5 lC\'p1_'B؄LM::_ގײ6^q6d 2Y*8x$K^5Y%?T5}Qgc@Bj%[%b\qA|F"DIہAW㓂wL}A >CP <>`XF8j[>(]0 Ԭ2E@ }o0"ĺ怼G A(h ,]7о^ ;UY.e+= :45႐wH A 4r#[NjB88SI_#j#شD0zsuS GUF ~"-᝿[}@(0):y@WҎ吩"71 LN(yE7⥇389-$\7#/\R"y?/;@t!MƙYtӠ:sPa*e]-oSpL7zu;tOХכ$*BNi5&ۮ& c\ vΌ>cs- >Õy=*tZUI7͓F7 f6^ײeVwv^yСE.Ǝ> P%=Ǿ^muK!QH}LzM - 6/HE286~ESV ȓPr2KcEP䪱4 D,:ߥA-oVR'EAhMޤ ZRh \ulBq?o|G&1׾sGk{]1}Nں+"n?\{ﻇJCL}߅R"U&jpRW`E^)զ6 X67YGB{zkEz|g ĕhGt"$H>9䊏,\EsQ{キ7k"洜P@`8xĉ-DI7h`22Au0L>K!DI$DI$B"$H"$Ho>SEYdEYM $","  $","/9̰pwIENDB`PK&7pLn]]~~5Pictures/10000201000002AA000001E147B599D77C785324.pngPNG  IHDRXIDATxSS[?oF_UT#UwJǶoCxg,MУsXR5k#0#99\@.& qHt@ `v7-w=;IvB@<yJk^k}g/րx<ׁiVu˿  ++?|XTYYY4+VUVVVPXY{ʠJj8c{&ja_&G&!UG302?W3eҐLA5ogX~%A\]xAUc¦mSd/&++w3C&=swT?AwA^gW򷦁f~ojDԏT ̡OYU)iUcܻWc|)+AM* n0:*iΈR18z Ydq򳣅tu G&N771o+{/](f/*Y*>-X>݃ԻmG}Ya51yܙ;EpL?*uFbU{LRv'O̳&Kڧ0)cL9Lf&?ѷz> 6KP HU?x IM>?NnvC=z5|^0\xƏ—I{>{ǰ=} /LƧUu΄n~ΝAls{~Ke2~t}O^փZ$qȔG1|`#P%֤x3qzT?wT,9}.#Ѝ?N|̮{Wg/2G?Gd;=/ ҋ'EztGUM/ᆀsG%@(vt' 7ľi7:\~lg (VփcccdI|2dY߸ 4A:< w!|R2zrj}ilW } .6 8!QyNٯ1 n1;}'V/|hjq߰Ԑ^.yŢoL=}/#8Za Gwnu\V~ի%&=/ܸaGl+:z^ Wի%D9ʣr{<0zۙ4uq#9}v_ݻW͂ Pg:}_f|Kܻ#x`L{be=?7nN@c"#J!wL/iuAZ&Ds7hzk098U|dU3OB<_B =A8ʩ5U#𢡊uJN~ }%ҩ4t8O:YxIt잾ڄtQ)J> G>^KN `/GaNVUnG-U=nVT}eOwdQXcX zB`M[ MbeeNf_z[Qw}uP⚅B%-Ygp70I l0--T fuSxܧ T` rh^ƾЊu=m*ONgB`.GEu'%i໵$i0wj1_ z nEPT?W}jCL&Q70VVPV5P]NFS!x^>pF<9Y0iz|f=ؿ>UVVVc@aeg+*++++(L=`ePeeei|TYYYYy@aeg+*++++++++++*++++++++AVVVVVVVVVփ,,,,,,,,,,PTYXXXXXXXX&~XYYYYYYYYY2~t WID~6k#VVVVVVVVTN kO7o||#XYYYYYYYYPxM|?H_}Eq.m'^$?&Dz?Goj潮f ɀ7Ts 'V -Y7V 5-Ak(:A? h m[l;\[ľ3sUPq8tPAaim˛:0~|:qK,|h8fw^fAXq`FuF:+l|Fq/ypmoX-bstGG3mѯV0i_TU_IZF~m3B@m{Zk#CuhsN?*Qj]Am-+GY% Dҩ( ㋈)mwܼG "usQ"V S^9 t R)QoE;&`'Tȩ'^꙲{p{R+i<;ű6*91hGAeD5MwS;=Q~Y? P Xџ$HڭV̓}/Ь8~5KH=ZT7B[u=\Զ~$Dhc¢^X*پ5\b \mEVt C硹- rc<\jMW҇K{gBTnC,+%h4[s+0S(G#- D)Kv7pɦ0qw=' Z}")b0sBbI=bP: -X& VBjQ:O@l-2"%Id--K`ϑAmTm i&Pe ҬHePeA۠wz6c?6Mػ=Ҭ@5Qm088JSgqT`١GA;XqYw1cKPoE :P3ކ~<# PU!V;* 7CcoC'|H/@V,24!!XK>|)s" a>!~YYw*̝z 85G¡/E|>4ntxiIx4%"4`!Qm7@/s4RnM$u7 vi2&GiPB"m5`Gw K sS*@P>H}]E@cgqng :{ "Gu}AóM\YTi_JL۠hj*A#4$S+Ć۹h?b4`_E~*T= G>F"L?H*JP !x^xs #Zr6¡zwgªF; Cؐ8V@|yEuJjiU|^jإjLO/5deeP-ZD'2-'ؕ/ki0?Yfee}[r<},=s+ [l:-޳0$H1튿_txw^# 3>yI#g; +U'FzPYB }NvTX|/{*j#cJAU-_X\lI=Nj.pU~YYw P&-[ y_aPcj;gK="[ :Nk u$2-鴀זWMUK|N )*6TZT˃lnk9/D+P_T%?ZTYS96=vj5`r/UTeVVPP hB3E:%GKԿXUoY?e >[ȷpv[8/_z}-lq=g2z?,zdN8%UZT]"\ɢT%TjK}}]&+m=V!C;2!+n@U5oF>imFccA5KV -%V j2_AҢjXH-Ԇϥhg%B*ڲo+%F 8 = GU+'[ KkE"݊FD w8p825Ak )_C S @?Gk兇EK+ZRa)}@@*`  18y]&l}d- Z_-ҟBV~0/QILط-g[B* }:,[=RXdi J*vf@y*-Bj*ǼjPut&t@)'_e]_@p,+ L]`<5^w؞~@R0j4Տ6 @"r%bP}$BB?SӘ^YLs^2e6 nX03!i4]/eo}K0uU :/;+ͦY_:jCGu'PʧnVs 9%ݶT2"-Z6=0c+XT)Q6!e4MgZJĎZFf[b4q.QT3Hf7Mǚ-?ԿuԿAU+b3??EMzFe5f]T-Uo]&#n߾o7]"j潚LQX]]}{ \fdBn3eL"}n'˂~GZMZg*VS9U/]Vb=z7 fw_m9P_ L;pl99cέ,h%Jс*F%=^ XYYY,Ӡ𺆬'2~ ,,,,,,,,,,PTYXXXXXXXXTSJ'8L|Aj.8?wA'7Ӊ$7ZT'vq΋ž OqmBI#yG=#,.km+Y7eQ n 9h zZ7G5-AmgK_wwy8`2(> 9 iz/#]`,H û0mj&o٩.cYYQdeJ>|nB "ۺƒŌCwӷ#v,bC?" 1ջw_|QUq=2hچ*@l:_ӶC? rAXJ&!@_&2ɚJ*I#@\|WĶY.G 2| N`ߪL{~083 R=f:'AI*꽦o_"gq[hgv]6-X.;XEeIu,5i'ˤk;ۍZ"8vrAիWp5KH{Nozm)H|B\; h%-ݿ >wShe_zg p=;ͨ~.^l'p:"f^op=qis.#M@&8y~|~ (J>9GiW2XB6l>nk-ZPg-6L]`qE YH;[91+rpD Jx{]k[d!_b5 {W@MGK@udjE.XHj6æ \ TEf53NKePeAآ5U. ǭP6@u;z{ðoDUՒIl4T;WAIXPEU^(AOBJB&b`2ݏgdQT.?xf67Q%Pθ$L恧x&NV7ėuJ?MIw dzߛ3>8=)A9#%(UO<߄ƢIsx~]@tUTJQ)waċ,\⅁ˁ ;-ޒV $H8 (/ &!Э]c4,9hҀOvM[+7oIvHK&qyGo. ˪ {E^&@,N7nÚ߮ECo?IVtU,:#&pY]06"+X6zn8TY><9b[[/,l]@umH7+Ab Al. bsaփ o޼_%q{wQϳ?ӿ'B#Ҳ3eTOUJ d8KSDYX͗OXJ#4'f_dpk#%OQHr,nr ;E9QRWkO ď5YT'Oi򝂪n1@jj3A>VloAFJˤB Z3ޜT,KǣnAN E%ӧU7h]3Җ4q\Zun}%>uTokPk5s8M9RZ:N G t"÷2k, [rxL&aqq>=sڲ-ːW '~[0e{V |⋠Zn[,}p|4(}RgY6JxH͠w#^0 T|lVsB&~ӭ5ߋU~~? ^~a+xS@ T7ᩫ jOLTEK'£ޒVמB*&hTPl+Nf tM^V2 ,tkm-XXaܙ%99ͩr,n%>dyOZT˃]0n\EqYMQ͠ʲ>>ǦAOLŢ:\mM"-o@"Gz#X$UP_೯| ggpr?]|&>-AUZ(+G P˩yWp@^=-X_ռ<M\07&w͸6!&Zf <h Njդ$YT_@UYı'ruǠnJIk'$tor`9yL`4oOZ[#}yC-BpW %kr&U,-Bxm{/2e,v>PiB?j]ɋ# ,fd6*lWvOv~^ZK-@>ߦvm0S*_Y]p:u\8QSLJWO~~'WKO O MR_.K}龯’zD_e|7d֠3 K@TY.@хaXd>}ZRn tz1XHz$ W((۹eJ-v4kyXS2,,L{KA>a y]e{Kɗ7h_n_@p, L]a2K_uT(ncMTv}Ti~݊ȕAU/_鸓o xST@hTBQ^zp iN޳(Ңz"aکt} P1"|Tg&ӴYiJ^j☉p|EӛVjD'=gK03yڱc<8POu/]iZN0۱[T|2B,^,(˴hT]tc 9i ]-}b}QgJISpPwZV;jiT|Tӱf- r::fGL2^Ӷ?bjy;F>i'DmטuaP}@u_-~G2_v~C _񛹷:^VfFތ9[2*"Mɨ[ NuMZfafUHWmmTX>P` 'zC7Wl|?z7p4x?N< KUXXXXXTA0Z/I?gaa1H4U5^אAAׯ_+++++++++ASU  ٳg Ӱ@Q|XYYYYYYYTEHunn=z_5*++++++++hE؀t: +++N* oV 9y\~B"i`0HauuuÃ;R۠E=tgrf%,9''4rAUT P5`-ϟ?'@ >,UA!, 0(E@z61uD5^(¥Hp%`7TBaۿZ\`zBu[ݿċC# Tpe`-}KBt j 8,( r.uC3NwS߹ wl6p#΃B6TPXtVBaB:}=SΕK7rwq`:FWKc̚2W5ga)9Ge*S~ʔ|9`P=D%RZ~|=χ{|QXYICj~7&=4s z+FwAVVhf6NNZ$m;Zp-_8# bsw{ m*|d`T7#BڹudUhS[GK,4es0(u4@bS@ZDJ|~Z&khɵ$ uV+{ !bQ/ Gv;APM'haEVor)ptdFqz憔Ŕ^+SӀK&Kg>5zrVgqWk {"vzL9A/A/@55$YVO;*BʼGY NպEF;*Y/OYmScZ0M"vMSTUnsUDW"49"%<̏vN[r9.*"RzԷp-?4u*AiY[Mf*fWhP5,zZVjG8,Gy}WWmϔ[zzRt [àjM_~C0S "^ ~/=}.W1zuFmW@ZJ+^zkc+yj1GP~8Xpiz@0*CZ]NUV– TUhPZ0P輜_uYUWkFL`a*Y=eTx8_fQm@8:~-k,i*Zy- CRPm`P9fчl,weޣnF Nx TW$u|T ROhiS|co lN# l$(^娞T>[ 6@(Nz`~ 5hEr{gg=pZfρ_8ͫNXAB+j%jQ|Z"Z9c/"]{2j~=ƈ,kTPGKE!B 0i"r3pEbVwZڠRQ ܥ yA/׭CdVU{PY'2So7Uuj1N6N׿xj$ҚTcB- ڠ@PV|IX΄]%+3H_/,յ;;5* )momQͦato~VXJi/U v AbSjQ%?nkVUi&VȚŨ,UQ(TQTPRSi ^6e CV꬜&+\Ttt!cn˥0v]j#A?!8x~|iQkDאd;Uk{hJacZUY _(4_h+ *WZ[Fa [ !s^6 ^|H>Cr+=(\y<-X~wÕ^j054PyMXDw\Q?UjX@ ipW Y:D"5\=0c+[evjYE]c_u{־zh"k䨴Hi yNyiE1GkGW뗌1}dsh0N\^_b0NJĪїXc v|[*q0KeE_2~PUTVMQ]We^z* @sK YZ[M1cPYyS|Xw\j,,OUXUU5*cSYJvN/,l9}{,5UZwzk* ~WkIK~X_w _wպ^ j hԭL02P>ܻwbLQ_%!O!k_ׁAA}U`aaaaaaaaa9 Z&ӟDA{M}30~gQoVo3W A ZH ERL_p?7n7Zugy9q̫W/MY;''pi+ < F՛YzW W 7O6wo7`{7ynm,6~ohUi,*mAu'uM.FDQP@ TPv/U MaCSERe}$8GK&l&eKO]pt|)ՙk:`.>_!a<Z77m6KZ qna`r6MHn\8M[cse*9A\ȳj-p\,ܲFAxvfI˖&oirmnXlY8.hnsw WneCGm$t`JG? ;_|lm,aH_xANvo(v^6!2 ךqP"nR85]{ZKȵ˼O MH>qұ'7oN#հQNs%I0j݂2R&o5|;F]#`uA|F rd@rToA,y/]ε@9tۅ>76hFn@g |<O1hǑU;Frv$AE =_oqzaXoy }]buԭCqIM`%J\^7wNgE02KSQuѧ[oK,1}A|Vpa!ںa8 rGoovcL/z/|djnpah)E)]+ ?7nap(`ơ՘<PPq)vjj :KJ0G$<͸NRO!! 0sr:fC?a)9#-g$T?H.}B] TH#h6 p'>{D;kkɧV$ѧ3,O fG(cg *sĜniv% cXV%Mi3`1Vs"k@pdMy^=NPVopz>EQE46`,0+巡znoLGT &Vɚvy/D~tk2[zIoܻnSwE# ,|wN#_st99+p7ܱ˗|`9\w%]j!2w/G,:N#hME)*N~mѳKRv܏Pn*Azz 8kב]'h'\E]MOyw9vɺ .#,Yrst1/:Ix 3OAT@ۛM3[g+ZSRuG|By`zJw2'y@\gh) w@ d6)Խ[>DIԀJ->,Bϭ"Rzk*0AU6gdm#wK{т[{g)ZXGγ(],i A0,>1n%C\YFK2Aok v...ҧߢZ[6}>+7+|TMg ZPgtd!aoyQdw%܍ErS8o\q?w<ϛ,EEHSӢT7E;!YA<",i=#4* `yU1xa>ŀ1 ffU2_yI@&EY@tΊzK@ƎT`nV[7݂q+`5܎e0nA\wZS]K jн5ʰ$ ;&V;\ =%]pM.1}dYK})nD~ xϯcD㻵!K7`QOoyԾ,ҙ}T-AXgnX>PPy&$];LJWQaI5 \zלcP(UMSȡVt"GDZ:SxNSdIyתl@-5siםoZhVbzT5q\Yj:v N^\&k<24[ K%:)1 f)$TuKX\~2~&|TQv.{gKrBKc@?&k4U)LT $(c0RMfv@$N{a2@ H:iSq{qlXK|qU[g fPH}-ķe ^ʠjDǞqJa +٪jT7V÷mDz<%Q(wVDq$E侂VIRR_z̝H|Β/ݗOEQ,՗OhVSx5*WđS`\ꑚZ ֣Vy(n}__>ukߋWOS.q/`f: x'̪ (ZhJ j,QpĜy ns[La $B԰fDD[oi`PgS,jV[TTċR*ނV}-j:bխeDc/y^_ent!FnOkkyXSNj,`nXoWF(*OcPe'P7a 3@R 6֖a죺ASǽyї IAAv=_KNE?TDt`jU[F+rt)cttS:)kb=*:O ȥ(Hh.`5e|>o:\S%S1#$ S4z#P%7 vyZDGu̠Z uףFyighO6\0k3ZNuB2? *)D#\I<.F;nݬ.Auq.~mW"TijqvżXyMk:za^e0_/*8V_ɲ|˕{*.]V||hU;iׂŘY޺_Qek{aN۲xD5wzM00>ٞX>\PoATтjY[ֺ:M|__ ~V8 =.zmV@(ۦI!_ Md^Qo2Z޺GAH`VKۆu iђ2nUa* jksZ>cG՝/~q/yqh:ŋt>7n-f0aI]u)W5(B܆X<X6+<"MÕeNo`u}b%\/pU]}Gjn; !l8W > ^YhX`ZC"FBS4=APEV~ϾieAt< \RA~n8 9uJ{ }D ҩHӐN)xbw MܯAV5vR%p9]lL{J U;Xּba0`5F6H$Hؖ=m(7/^<2\."pRB?G'6sWU5@Ti!o@ Zpk|K,-yq\{ ^|۝pYZ`C(ul!  oӠƼ"Rl@8ڤp*n6YD>b[K#xS]Sp|b+z W E*z .44,k&usRX7kS_Wt@U ݦdpݻ*epKvlNUTnŤsbIO BghuLD`[p懕\qZ|Ԏ6u클 XֈJe碝tO% /=6;v:U~|̠jn~Q"b?e*"'ӡ1ah1B8T2o fU2;JJ㔦um0ڋUF:4:ð4Di\ @Qn8W[&yUé}L؄&-NRz:&z w3xCB9TUqy=axlz+$pEBmma*D{!5#.҄~'b}:ȗ/C\ q6QO+6!0D눭gS `_Asz[YR"h;ޝU }48i%S6s:{U@u ,'8uS)HDi0si Leb #to:Aߠnќ D侰uQlj/ oeJ_Y/6K[Ž6>9uc*-}+Kcz63}x~3vWU%hDtJw%p}U= SwΠa*˿T}=y%V܆R Nv܏aogWZPa(TBWKեs%?Vo!E7h5*:h]lVKH@â_+3SFPq\n_ǼS2boPzߎ{vU5._YmȯR/ W2(o&uhs9;+ۂ* =UN;$H6Y0UZSb-U}YnKHatkX~;Q‘utʰ*A58gWc+=j~[T'D/ծ?b^`&F ;erSt']2tk/'OURFa~~>S - Ug^_Ӯ ,EPLKgcgt0P[>iG}60tpp"wcQ]^^.L*"+nFՎu⡇p}T]NUd1UUgYC-CzS"Ljei !SyzPTko=Op+B⍷A퀌^P \aբ~UXjˢaQirLg!ȰzMkD_W,6/ht:]Ui꼯"_GVMQ`dSj׵CwpvwSnLRES(Sݲ\jc/yFeŪ߭EUYT\4Scj2Lm"ya/1vvZ^|IKN!%iL?UUXSJ4R0inBChw'U= NIӧw 2P C_fTn}u@T3%zY_,8\׸84^5|9h -  Fop桼okKP2ܞ~r?h)=U@{jtVEtN$@jmQ=DY|<%S)]XGAUiZTd'D}=)Q\KU:ɚdUr5BJgw.<%Q҇dP/B8GAzfGmi*g -U<೾GUOk5Xp%2jE_:/x\r{*^( B,5R1 %-.#? NLRy8C-Q4 1ä1Zr'[og  1aiuP(@m}D~mǺ5!R e"S rt54T<jj% 5'> }Sz[o酡MߍCf)߷+t߬@U_6(Q)i JXReES!S+U,ͭz\j>Ek!ݣ~JDQ~V i˗0e_4U*N+FOIԿ_?;)Uvo2^d(1).Rk7FY5F @&d0x"uԴUVfOq%V  \yg}[Pd4J ԖJ H/dB:MKGU~N(i+Osˠz*SC 5VՖy n/~npFuTS4% 﫶8E֘Nm[4VT:)yTCpi#?mzti%)6֠kzpުsL\,Xin1ӵo([^:pźuTe먖z-3YVjШbz<s ¼& -;MMST%df1j[jzU+hLMyJl`euP=.^+4[˳ zA(kj6->Q_h+Yϴ.UڱcUloQ ,Q5VQ~+(ZJq)7,h9Ep8__`Mϡhni6Cwc[}o]W6){BT-R.K%-2A-oUy6M.KW7l]s'q`P.Sм,:B*@;dV|#J= AXWp 1jVf:224P( ƺHbk B*L700~?VT\*NU_ xh*TRJ "AڟL&7.WUܫWUn fo_JOzxÏ$Kr[Rl6-~и}6hˠx4 ΂̀AUῬr9o~COE ZUKnh:|t**S'y?ZfQg49g,^z#[]O6b:A28Opt_ϯ '֒r#'/CP_\y_,].x,ʤsZb$d" n6_|1[Y(BgA*TӃ/MsaTMGf>/AT*L VY\ $Hjjh@ N-բ :TKo3x{\fK} tM}-td)y:E|\y2Vh0$-U4B/;A޽'mI,TTK(wlx/՝k0#|P9x^*ک$ȟ:5M!$IT sU=֘oJޅjc#uJK`yQMʪ[Tǽ9CtWP+ "#ﱒ1:3vve3cx(3jPQۮ6uV;oWNZB|؋$WDEښp\\3\xʩ[yD>~!bղ$TY eIYP-e}_yYby^IH {ZO#[y /__4tc NKW5U" 3B1CA^WTu![Pκ r5^l5)4`%5K:OlC? dΧ5W@f4U|0!D@*(!8nz/b~9dتTxJ"m|J(YX *5W"@^UCEU 8鈏Jt B͍uuUur TӰO(X* yyVR(UmEv&OӑX %<⿽F`WfX[M@,կ(T3[F><0OކEs>^v/fUhN+O{aH) (dAxH(~?-@Z˃PǠ*T^|ZfƐa}^wao4`_UҵPczg- ]ճG6$~=msuʨMMv2IKvm3U1<'=\9Qe|r~&cvsKo¯M.s%@$ţycB+ {TKT',a̴.[yRY$݃XC<] .8ʤ*ț <'Udc-T#h;P57)]@7hs+O, n6YHm %] X$=%U t}|1R</AU0p٬O?u5H,B.BRQU:UsbSN'r f ?( x+.)q՞g~MxHAOP_{uĸaɐsr6o'r Z!M7p +6LPn pkPEж$ӟN+Y(]egڗ!66)aĦj\tuS$s~AMyX;q#A5`@8@u~.ַ [|b[:}|Rk1Ӫp5|%~ t?NB )ȋ z|bZS 5'~щgLTm8V1N2aT1Sֳ3Q5ځunQp,JwP7-v1`AQ:.`H+ư.ָ׹H= \^G "b$uP?.C oj^.Zp]vuӹv95pe[ad{Pά9/QՉydlT+"r2t{k O}ue]U&M <vҗ]A8@VjUyy%>+/3[@S~}"މzl] _C°_Cq{٫L&q1IhA-sŵ>RV)\!xR _i[*\-L:AČ-Ԟky O"pV͘vz K]׎Yj hi{!&0NC.ͷή$\ \[%p51DmvAuEr0^?(3Z\k:}.GB};'}쁮CU>1St"{P*Vu\%lqtQg :˗*434>9sP-)U+YT;JcYtI뿢aalwfZ'SSf6E vm3PZҚX'Ol}q/hnqBsV@n~> FsT S HL{;VT;Hm4W IARsO<0bQ]*qf?>iQ}s؞~]~P =]]C5i~[k5,xO[n*cc6Irlb"4E+bb[ =rsf볶b8hs Pe~bzZҠխ/:X^Bg!%- zC ̉[$]jek.=TT4LVA>##.cm+gYmIXyIY*Ccez͚.eG*?mGt3wRc%~٩~/q.\YխXiTgob%T?UWGVa5F*Jj=YkU3\O"NFQE_2I=c{3ƋOr L2m$Ti!L2$AU&dI&dI&$$L2$L2*N&M4iҤI&K3 ک]I&M4i$~6i)ߴv*=n*M4iҤI&AO3]qQ\6N/gI&M4i$ޙP@?WpoF3~?fl@?=S~=K?tyg!ӫ[mJNMX8(i:=\(i[_yFAks22[yi!G@E!4|uue)&+ [f9??3m#[! m8icy.>2V7!Y{ll<@o_qS^9zo^:l8)~<^}}dmv!hήw4dkXvO)jӤ!i_sإ<6}+A/xcToX$w>O?D _vXWP=߀^v!ۀnz?}uw J =l`Lmίa0@^%h0- gx[mjAU._؀7?a38::" m0ږh+S# A/ xKIVJVe}療)$[yfM}}!T<|a86`8heNO\ø T8͉Rݍ1_ln+ot7U=Ajj8g\x` N-̈́&2WAor7?u??͖+8^nms|zaKA`B=Vq? mF<Tͫ >e݇W/|mQu=u 6r`]z؋@X| ShPceBS~WT~o2`#qFC51T&Ȏ (&eO֩aX4]ǮzYy3'R6eht頳*Ы]w8.36GkqM[v݊GaLPE+LL|\< T-R㫁jԶYj 4Aմۆ~QTJDqAEſӨ`@vԜ\!@98m uvv{pg9@XN)/!+=oQ} 7v- ,G~O<iӷ˰+`>@bn_r6LCClTo^=~A8`nF>cj= "m69֓!y6WaHg;݀c"(z4x%C^RB | dӲdԨVo7~\0JPf ?Nym.Q>KI8:;<"h8ы6xbhkksDSQ (ܦ=F0X(A֖Q6KEUz)2Gtxj)~p\>U8~vc|%cQ6owB^ d_E&ؚDZ_S&o_~Ĥ8 =0'>w=[j=6~ݐ &w3sͱ=(8XZc Fkj) |U8k#n26}RPăXKUOoP  ssST͗`3t!1}<RKzfuC׿?MPar]째.6TKl-doF/ܝ$zv A>YUf6>}˵_ )p^sVcֿ}n$); op 6p@p9shLCVmA_ڟ|k'pr3 X5Nx%\o\ \\tuS$|+ ЎuqpT?PG0;7LY1xgY>6#Z1;mQmC+K1YѥoNB85̈́ZsE=+Jun^9f;u7ڰkIy|Ea~bvH+x W6˫m-@߀?X?﵁CǾna%4PSHlJ3vB%p/;ޯ>pU+TxjN>>zo5`}oSFA9.>蛚*8ϴToq| qQkϱ\Gua0t._<7>q`[ |cs?϶:bT 5z^٬j7g>Yg7m\>sE ٞ+qj*L2$L2$ϒRq”7׿? 2$L2$LT?OB:>>%p9o*rU2$L2$L2IPA.'\ ©(=Ng\tŋ (~\Y?M)ZT&dj++le Y)U!~rIVv7bk6TװZ6 7σQns=0*AiX,wxU{i=`MT >}OI,&=v;.5hɫIx $K 0!b-Iߜ%!JS% cDӍ*mc' X3rTlh jUGmhveUAM֎iȳӸ^)P j379K5>۾Uv/2o$e\9Mr29Fx<65S)8w]#{Q5W >9+~DD2,j}󛳒_u(?;RɋzzzJj**[[[DVWWiU+0|ΊPd5À+H@zҹ4*D8{jn p}s ?ʵ@mf P%0eyOn`ݬ~U+ϭ[@)\QRe&bU@Y F/%~ɀ|>1ؚfx!% IjٌG4A;0GG<]1UY5K-j1ƒ~^QV~18􅡜e7zgL*l݂I짾(e^H9XidMg}mG5U黶 kifwJPmT 2 ?l)@T`=}`u1$P%SKkE!66C1`ǽV.> Tsܮ'Ak I.Yjm+G5n4>{$T.2^ыiK-%߮~XLe!8krLB:Ġ5DJ {K*u0)qUI{vhղ>Sf\JjJkElni)0eIDAT:Y`d+`q,I r6PhbJOg$[R3lyUԁ+CYUlJMk$+a=ʈyuFǏE"t=zT쪖֢nml'& u"api)2s"A0PlY(Fޅ;2̏Yk5_Z1{5Ք^`/~?f뻑DM4=^fY犯N y{2sse;A1꿠[V!o4=1b/e G%`G`O 9JPA_~!5_cXwZ B;s]!P6$`)֥ ۂ*sO5/H+ 4X| V*(6,AniEZ@b RYB/03Q? dνZ¢ k۬/ psXG ֦:BbPe}:I͏V6{"y]Da+Lwk 4-|b97x KUQ6 ҀK/!E$mjZ!HvKp`0\-םY咽a/-9c Njt++`kۏwf Lf MР,U5kP1 VG6WPb/&P+ |VEPdIqXv-w+M*&Y^>4yQX*w\\qއ bai_XKD[P*o>y-ˠKm/{^"" *-@,f -@!0 ~ * EP۔ 5T' ݀&a;Ul-@5@P G5L?f|4!՟ Ny܃'i'3GQO,J*snAlzde]TP"ky+ՠEC)qUHz, HMV?\^n =N u˷2$&&VTyL,eIN妪&۞n=t׹]Ѹxѱڹj[kBtQ[pU&u:61cfd-Q#-UYU'x@,Av3ȤP9kF6a7 >\PQLY6ȎqHe}z9ujϕgH8zPjxcy\nr^%~&"QTs+0䏂܅ŗ=.F0@(l}T]6DP^O nJx꿡0kIX&jV - CVyY7p]nRwz[I . d*: 29SBɌf'U0l$ZwQmVa'&k `'zx締D 93}jrrWfEKPoa\*أ5TEXMe G'fj|ªJ"A- Se.0Xv[+A%0g瓊-J01|RT.Jܨ*WO~[ܫ~}#\| I%d@{-+=>W\@wKP1\-aT^W[E9"U S&_U3fBb9_)ɵ&a0߾:(vri=wz?_)@WUAOEA%ŋ4`9%r{5Rsr_,&ů.j{   Ϡ4pٯ1ZN@}+QQ%wח3jY(sb9mJ1g) %?VVU7z V' Hkk/+6ժ6f+N7f]Y*iK0FA\VĩUlX7j&c*An# jp +Y+WV޳'&*_+/eZ1mEϱVVzMuP?MR KU .x&B yCj+3+i@r [ c5-A5i,1:"mX ʌ϶<+GcMLk!)%xn2}TE_motS$%4 Ţ$ỳZME(^KST?1Wv\d/[MKe'MF@dfue}l{Y2|IH2+ҤevoWJg]v) %)$L_z1x C2ԚP[L&$I U>RK}p_d,LuK)$AU&dI&dI& 2$L2$L2*N&M4iҤI&K3 .nll/lnn/T,JFҤI&M4iT?!kJ$J&M4iҤIP<**gv||L?zuuE!ϪgVҤI&M4iTtP_pttDnjL=yy^~z 9z~)󽂝0ozw\_ >}ކNy]xFvqF}{mtIˊԐntƭż^w}MV6֟P dIP̠ T|jb_TTWVs2д}O60 ׼pv|? s ~m}}?x0=e3][i{\>֙FիW5:lfE|-7!Y۵%?xoc;n4 Ojezg!c쌌<%0;#騃ioBvQS0֏bsl6n.xƏZ׏787t|,']!3 bZ\Wn7O}yl]]fnkHA,}hVka(AUR*u݂UTR(5H@,#2H~9 v3 D-aSe+`5oɾ 8:tO^,;nR([c`h88{q E(hIjcߒ(@úPZKR0N sBy:Y 9 0@}bW1m^._BI~83nKJNN!s S^ܫ ^]892aad;O"';E9$rW4Ws ywޱ=wB k0y }A9oڃ|P- g{v. Z-f" ~' 6K^`p46 b]I1(o>r:z<Ġh ;@m"\mpbuq%zT9|jf;+ԣY9iIbq[2(yC!qT| T^~&3vC&z16TG:#q36Rߙ*a@1{1ր쥺7_ :80Ḩ{o0 OzX!^:,rT571 .!*%8A FzfFÐ̜-r {9=ei]?9n_O`  ]6Xl>q;!}dP:ԭdh T&)㠰mz7g}6wA UT<uڷ/GKBUubᱹ@{!Ȯ-,@ [*Z*Srg\؅ Ʃ;sgT ]#V7BNm] I>Іd*cީe`iC*Gx>vD}gi3KPA!`70oJ"b}[ua@ sz{>"3 "LaNL0BTxX^T䞚?aM@`_"wmC|!A_o7WN"H? 9;hu_+pPapP`b{>d`~j|w6/U]:+Y= =oSHڋ`m o6uѥǡ06Sg߯^lCh܂ƚ/oAm67UM qyN|y(\%~nŲuq*cTsV=|zKד}2p^j +~Dծ&'6r@/PVT "q!Tyl ?W:;bssUSO(P|(wsb!Lc\v9VrV =!X 8"SMޝ0蟅 ;u&HP:@f/B|.%^Ԝ$4a ~dwo^ۀj>ǎK}(UYJVRQ \ԄSjƬ+8c?M xqـᾆY>COK<]b'UR)qIs~@P&P NNCv֠ZS)OFA ~O@wy {~RT1 ۰NSKy}6iXBڃ -;Ajeq~8{+QF?o^۸C|:WC<t>R@3tHY8Ne`2CkorW8QbM?Ig$VN>l.'qy Ќ}{yq Tޖq{Bef=v-9/ҹ#j3 G|2ևBvq2A(8&ZPVHBC'_q]OS6ED]Jh2VpmY808 4~:Mu uTfgbujK5C]qw?dI&dI&$~jj_W?!aU&dI&dIIe8>>NE,C-J&dI&dIS?gNEt>___w /.+{s5i ky#mj, z] Y)U!~rIVv``=0*$~fPńj**JXBV7{i=`M >}OI@5Zl֠%^'Q=z^ d1+5hP?QqV` X[\Ozz291FmeFضXMNtz* ^VGD4 [#6oJ&k4i\fak/KU䛜m_*\Vks72ZWk9p>(؛gF:&2}t [\#ևfk;Qw|eTid^J} eX`R9lWe:ʏ?jM!,'/T[) nmmѯQ% X]] V '8+^C, C= κ!KC:B4ghb5#m􊟎 Z6Y]C qvXjQٷkw٦)~`5:Az.ziUۍ;bXMCZmX L KP-ea"xlVVUWil-GJv&PwFJ*igWYeuK(D Ke^!0O]rܚfx!% IjщG_7A;0GG<]>A'W]KlD l }x.{9- -~ꋲ Qt[>eXAY_sMtaRNT] -@5JA&!Cw?-AWhۤFl1X]& T BR-jQȀMA /6<EKPE""J.Yj!0^ʭS}$\$C͸ы!Rm-P >/:tlst`ߦ? B!Ȁz VC#XmK6*AAҞ]38@6pF4 ,'> `TR#06UP_{(bDtNc+C1BVdkߚ9P)gc&VT9|FQ-5WExy2UŦTFb{>:ϻHgB6j]{TTTfT|{ޗ>W|ujh5{M-^6+ ‚oYl74=1b/e G%`G`OO^s =/Bj/7EC?ưtUsEO;sšcT҅wnkP_qHG!R=^sH O()]ς&aրU?Ǹ{vy9?xc>=C|Ŏy֠^x`;Q{}TօJ6:Pe}:I͏o!M D {Z]4lTЛ)4xnޕ1&.Gt6[_i(04; KUQ6 ҀK/!E$mjZ!'a/4fpZ^wT. yb\1:%/ӭܗ[tZքZz'a-,`o RQ z0O/_3}rMwB/Tc׹ˢT[?W1 fEІt*T^|Zb^B7%p4" *-qF*Wo5 -@!0 ~EV/}ߦl)0g- ]ճPqƠq;U1<'=\9QӏٶgQR|sb75+ U(MQ?P!#Ƕpb^\\0kljO%C.,-mc 8dR5R=_(xk'W+KP"١flP.T!)وUdFqrCOA䓀 K6S=[Ǩ6+\D[ϙ?NoAքb}5;>wP5Qċ ŇrWfEKPoa\*أ5TE=|"TIKC*k8ꅀ>h\ף (-ﷀ+&Om*e3'.)VW57{ɺxdxQsЂZ:#v\qU-ALjOsT"e/⫭"rYRQJ?@j)EX5cVہM.$&UdSYj hi ;X4|{-T BIjB/Hq-2P%]KCBЋ.KQŰ \.,QTgQn@(j .KF.QX!E}{ZI#K/`Tx",ʙWZT[*qf?*-/~N?wzӯO`Qh.dw7htd!1~[5W9%/n5!{yB3K{@we65^X+kAOn]סv>hfy]~8Ӣo>!Taţ"9g JkTTzY1ۚc J4AZ AQ|^bӝ?qRꛄIoʗiFi[g1yedm1bqVrmPaxnJ\8;U2bBʔ|;[AGo\Gg{1V؞vuvɶVGj=WTgLMP!{3gOd #\2/]WE<7|7IPmmmnĞ1"]~uonןBdˀ@eMV+P~ iB?XKet !r/A H(lլNl/՗m/_I>W>Qav٥$vp]+L2}D1 C2$LTЄ*`ii>L2JYeI&$$L2$L2$UdI&dI&:PʼnBҤI&M4iҤ}i&A?Z;W4iҤI&Mf\9\__ÿoZ;gKh&M4iҤIa^!)m!_T]eI&M4iҤIP3!6R~gR̈́y_~z 9z~z>ėpxz\/v6v@?يz69H݀m,-4VԍBsԍo(i߾88:iҧ d}Aj;MP~TP{LBz,Ӻr朶mzkߓ= 1 ׼pv|]:tqq:e]vo  |d6cްmO'!W4+&l; B4^;!R֕pg+^۾Њ5UB~G}C'g$Z2NAeDbttMIse{8ZD2x?΂zR^򗿀xw3Ps΅7 BLxs.sTY.qSp 0 raO?|O>~;x(S+ !Da0Ua/5M1joɍ="> 3'hf|TKH\oDz(yE!HSe깍Q0%Py߃{=Foinޕ K"[TБ3y*r( W@{X|Kd!;d?ZMPz[nʙ^υ{OfIv[¸ꆘ\鐓3Yܱ2ǜܻLk#܎ifAHc %$pc8iIA"T*>sJb&+U'{Zp)oXefPm][گ7&~J,|viԻAppУ6!c8dCj )C=7t01VBBINN9t̫IMm699/65ãLC&scEJ{xveL,!<̏ N2yODExUO8\br g~NԿl3U[S dPOkGz!w}PU&OMYsW׍4"z!u{ Wz r|u31zyTwL)9_X*POJ2AMr P#8nP-~}Hj}1=fL ROZȔtFٺj1&΃@$@W`y|U:o?0QG=1PC" hmЭа*稪,sY%4B^aFP}v0'ICڿM TN5ՠV{#]߾=ܻwֲC>y]]\tz ̕{Tw^1%˺v-$=c5B,)xz( ΢i5ݳeƔ]v lLW{\3Է+7R*^5f;-8"|l0BIȔ*|OkWxƾ5m">q;@ڲτu]^HwŚ-6jl RPzUXިU`hQ=mgw>-LZ F?w{LJbMLJ)dVq+Q)~̓J=\rSmүsxzoov^v?z==/=ۧ:rhKl–&ݭ 韩*7bQKٿfua]vԍ%RW}ס?ho@EP=axVݪx}PQO*,2TDyWi*T-5 *)}u@EEEEPEEEEEEEEEEEPEEEEEEEEE}@PPPPPPPPPP^CyAΦ/1`cc}) + trYW_! ^T(!NassZ A*kf*l*}5x,%qYt" UZ߾ <y( ʋHBB59:^䌠J VǛj==o[hXxsQRS0T P JvwwY*USme?ϞD}Y C`*:Wc9xGi8.r$?7F]>ղd?TOFS<W[ggMG^„Өcd>m<7]7uٲܺ5Ndk_kWa|f!_m$o/C!hi>myOxCƫ9 I#U~ͽc7괃~&Yq6/%6= kO>L_> ݉= vz x?qݗ`QP^=)+*rf`;I,A8O98q;2nÞjgLԡQaBHD0ReFtN69P (gCܟ 4r~k:g-͎jA5\>M,A: G̰&98]Nv-de7ꐏLNH5%pT\=g5C+`y[f :m#m ok{N;ܮFvvvtCAu?.\۷qpA WϊeD/{XfAyU%b~ۗEWAi+>ք6pmdy51'r*!]| Bu&6 i]q'/s@diZd}qo>4ٸQ('1*9pCqz[ F!U˫0d͖|43#iU#~ T˫lղ@:FZ!eh4 7ZUG96F,U5ۏ6a.Y: `4lTY Roٮē!2$ڡ%:41PL Ku5Xw,8k De`5!?8yMLn_-M8]01?!( (ԍ{q„$w㐯1(Տ514R+UI:*Rjw^ k4NO*GdVeRoqRanWH/1EBd#b@##PnM4fϼZ 8;!BAn C>a`;TAȠVmϛb^mcu#iU93.qP' =GX$ӐpP'U=ݣnQ5lƛdii!11Զ#)T$OIYV(kGLqd[T>]d_ͽBP=aPկ~rQi^* ki١&P@aS8H2ue>x. [Au? pJtn9W(o  \kh4tY@ԉ6*5O23(`Ȓl{4t{4d*KWgF!ɾֲ7l`m !E NB\0+󒹈e2ˁ\\H6N A5-oݶyR^t;O(2҇5FjM].ЖC]0"YȪUG}`zx}|_ FQMΒs2;^ZVF`j}>iuizEƍsj*{BǰͽBP}գ/ Va{çQW)sT/4>ܾ_ x@U*Q.Pml6ejW (KS3$ĮS i%vZBviyT-mF^Fޡ (s:w4H.IOAg3 ݜct~cݯlVo0rZ3Tr|yڅs.yé'[#B}C9Ϙd STqKFړ kQ6Z]nÓ&<-z 2 ߕ:9+ 4TQ"P^ɐrH2mr]Ĉ4Z C]9a Bڜldr-37y^awjF!ìN+?o[5Ew4/q$>G L4c3t4EP}{{h95dz|BH¿ 'o?kJz`W? @sG^DF d1OȪuiFZο uYx3DME,{:TV*y̆iz-%~%T\UN,ͽBP=U{L>f>'է6'`h)ܽ|>~=WGx4Y?TXmӄ'0G@O@2Gy@U !O\Xe^!|VOL *(k5(!݃Q01WmD7Uyaf<$fzV<_1PvA5@MϋYƸTkU,&xT]dqUMyնPajAE.7l6rT]sd_*C⁩ѱpRʣkC_C"B1S4MFϽ_"-㑶Tt;Y}*5~i?{+̅'o\{z b{T(Umgc*K}V>ջY};.\0u?6ƣJ`w15SO%&6((T[G{pLD*4ic.q=ewڬWkNKZt4}4=db9,m)9Gu|Y; Gmߣ:&t𨚼]K\i[]Ԥ- =r{mְBM尮G,ǫd&`Sߨ37AYU֡廎o qJƵZS\!S>xkڏoE0}ۏ:̚f6 Hmꉁq{t9`~a= _Oju[j'uWmzZHYnkV'x8q~=٣V-C\} u[?Sٿvn<uԠVoZ:7,'@0~^e ׏H$Ndݓ5yS:x͌VH7ݜ?3wPA1Y^c  vT]U!WWbqؿԔk((ovљ㑪[T* * gϞ*********ǩgY`1/nϿ7EEEEEEEEEP}ug05 ܯM>G/kF[_{YW~dهg:)Nn< d"Y/d_wwv`n=P: 6RTЊZT'2K@uBPmзR}[Aė5Y*׿"{Iٻ_T/%Z \9s.#eJ.++/v;+p8s~"-BLO?,vU͛6ꄘ}=jblďYhXbc-ڟ $TR/t`Jz=NBe% bD=ZJ͸b׏G#=5zO>izz~ kB)}|Q3G h_ڀj.18=kƽ^"{O?wmPX ah X2]PWNf]Kd2%CA+#sd<ɃW%Ud_x:ڂ9\ C2ٶ2  :ȵKvPL /nloY8qA4ji_XƓ StoqQ_sP!wB*]N˻ԣ݇*51I| Ѿ5^c?xgÜ,Ν{4Ep?0W+;w"v(^bo~?Y~iai+oy0!8fx\=-|Vʁo˜-xC݂J{C 9)a=9J6'ķw NBXb^;3Ll:ȴp7FQMw@I Lp/QZ`)n8l e&bA*6">ȩv}Gwx>U6> ݛ;p L{C-O :NB:k{?u|u-^8]Jdd?޺=w",7@!w77 4E\(lԭRvN#{C۴ Sloz^ކ=losqS9HhP]p"$7.^v2f(+ [^\|rwv+yw% yslc&dF)M@fejFkCkE\fˀm.¤t{mU$,f+1XH#CPE=hJT+Lb\& hS@UI,Tx@x)eAA bz9Juս=@*Mw[ xW B1Y>Xo؂jx̳:cX=9ν=~ܙj{aSwN`S9ɽ];LyW66VTUGė*r7k - 螐JR XdXhR%PB>(# 1㥘 bE Bu#;tjCpR[EHYΣJ`=(07RQ&Ġ?5TJ)=2T*>sJb& (Um{ \AҜ7^ 9W-b d6PVpePvLJ&P| TohJ' JC *]N,]vv+@@Er7`3XwP݅EweB Ӑ:Y7֬ܣ#ul=+K#ilb惩 aDlʊWR!=l/֋M:XF;cfn2rZ>;-[R@1Al%<.KX}e@KwxwɺGN9]\_]-c=:@'pt7Fy6VTUdhXbSƽn`H=esZb^1/w) oN 6_`7 )/pUʅA/o:z|L&܎0CUyV1j^~,8 ZrTUw"IQYhe}|bW؃eEK/@P}3ANr\]O2~oƅ~L~iw'ڃ*l1J GLJsJ݋`9ݣJ>k"S69J۰Kb+Sw6ܽ8n66VTUnP\4 )rfy=ZY1!<,LxBʪ : ^y+렪lEMa')(UZC,oö[bf?ET)_' ?O/mE+9 e Y1ښPO3Ldb<Xm" (Uu"mqeQUY2biE,AUBṶ{-ϿMMp8!zO)nd:|h+*ްܩGǽrNwa+ã V[׍걾G=ܻՔVp`cE}kA5h!`i yBz"`a^bT\A> "0&39;= x:C K ™M9aږOJi=-ttbB˄/dv23%*Ъ-G=bY*H>ӻc-l!@)k_wy?բ.o>M7\L:0_HF g\6}~U=Mu:f `#! kM}eιC{xI3kiv/y`@Pmϣq?we_?_6:}lgN>'&&ۻrR/+99 3"i]&#=7r̝dmj#kDU!@vUYPU/Ihɵ;Oͱպ'uaCƧVhzw4g ȿ,~Ki:V+b?wVaݾ̀54ா;?m(YauP!4ax\gBicP07e,ҴKC#rP{oQAc{f!MRxizCyOf϶@%Å y85;Gm:n9 =p -k8.Y|y܅zڥgQ&m[Ch٤miJ@|@Zrd=]"Ml4L@\xyYpP.<#sZTDa=m <^99B`9 Hi #Kj*;qrƵs"2'a4Јk<6X_q!XhNljzr_?T~mT)`~L>S=h*'&ݵ*3Bt?a#Hl?'9 `o?rZM)1nXHռ YK -2)[tdƒC+v)Wslf/3f{DX}̻?5 t&PPZlAof0aOQ`Xxt(TJa$bvS4exF,g `C#6d?19S- V hw)pkj3͠muIDP=aTw~mɣ<}"{lT&|̋j{5B ܻJw*xPPTsP)SF迚.M'2=K14d&:dc7zf#!ր7t,!:1+e:VIy P8 a*Ӑ.BATK5̪aL;aUwMu] _o PӼrBZt-n[3(=iv0nD@X#SN$p.ڤ략=XS9~dq"(O]oomځ*Pw*yxTJΊPWơ* {!hզ0,5]%=K̨Jt+d)iGs1 .Gx`ՔߕeR\o:]̫S-P0Uلy??KjzȋS[Ш1o5GUŤTvL֎%/v|h\=`2`Fu1!HC_4h6zZmE)C0R/iXw֔TecĪbv8N77U.ݦ#=m ORP}RBzmQ5 KPpСVf,'lXçVf5hr:{ {#^U>MCfͻMfض!Ek`Ҍd{ @9>tVg]m=݃;eTO4H)VI{3 U#AhZ;]UKP=] oRT6Bf:g" G!X9b}W{]JTm B&䚱M^1^AA,R/hU\}X-_ӱo٣de#sq2U!K'd{Rѣiv^q2VT4L8ۏO2k"}Ҷ`v:NՓ݁hS'U=&X ?FkGuMk ߽j>xU &/O5_cf"Q g'M9=oLfrMičPwI:aA@Aj 뙸1q=]85c Kjnȼ9MC˶Yo01^7U Q:DhxzЪnWMѵP\>aHWn=!d,9~[37]0v.v fM&Bl_,aMc4U0#yr:d-Djj?>u8y~CNljzO>z$ۼt~*(om%Ouw6:HYJ>x`hxQB\~rjƏ*(c_xծסFi72m?X/r'" t7b:DACAAAyAm'U#WF$ * * ʛ Ϟ=TTTTTTTTTMxPMFߘd2PV&"JԬx<Jo4********WE2+] VKlTTTTTTTT՗H*=/=ȭCN;sm@x1 ;{8QQ_VTEb*ǹ]IYAOQ+oƵPKz㴢:"2# nT\*LJR^S vfYwwWՎi+{ 1iO+\;юyl^Q]wooƉvk)7oM:\r1Ki?_xbg*sB:^VHXuz@*,K.Ba8QI,tn26>R F], zQ_S!~}m]b{AyvR%:!>C\yڵu1VUu/b. ќm4~ ,O/`پ.%( C.#2`;jzH]l2@ uvQA<.2p/`k+Tn>S7+I L ")` žR["Cp0b>Ts ̝> @-g¬ɨ|)8~ , t x1n=l{ elUwӵZ\ z*KzO.gH4Bd֒&ZoL!21az['u~2L]I>AlJjI@vM`[=U4 tաPi~3(0`Ue[~Ax_xCQ7BDiU^pExl/^=Zߨœ<{9/1FE}< Basuày&m' ̀(<<7'c]#b\Qm> $z] lTPx\" ;T2>Vds2wtxSA78'%Q$dҪ};lW 86G<=%8h3W% ϼ1듰%W>XoǨ?#b.>cz6pE}@CdNH]r[N@.̠1e!V-6Kjw!"L<9Y*4gh/^maJ *BFw~ic= Bd~¢ T䰱<уRݒY;%^d( IJHmrw!PEy9~@ TiËA)إsG,w4p^: /=6JroC5uni ]W=psPQO2IA .h4|I!h+< -)Ɩa9TsaH6oGAKYbKPrոz ƳK*%1TX(gx:,hNݞc*=O0lEݰۥ*h^p4ʢZ aTԯ+ 1o3{aNjzeÚG9Mwn e2L)a<r"$nn*LB%YSc7&S{$0-jKωBEiፙ" Fz@ *j+^L:WnM<ҋFƞ>͠lo@=0 蠪PUtPuXQ^.I.A\U:bkv/Qg[ ThpO] 2d7=$Ǩ},Jk{t}UqˇÏ @m3賤}ߴxZߊLUK0Z@AA2IJ"L]?B+t.;GuiBV*|uã1zyTwѢ9*,P@c`Cn ĄQLsXPX(s\6B-97j(+r |mbg+-VJ%(׮UzqY ,=GRp܄Գzq ۋd O }jנ*R \+ lXZB;U &]<|PHh8MRY2.’X` R4ǩ8`!RGhy0i goPyrbҳtbShj B"ib7\PƌAdDkpϑ|ǻ佅 |}QYA}yƣ |@-iB>oΣҨ/Kr"H 5dT凁$T/A_>D>Vj+eM^RUIVgIϿs$[ K8uA}=,?*$8mU-j9?%?[g>ky~~l}MPvԖ-'C izm~8RK:iN'|Viy7;K_O䳟 he?T@T}ېM?W) \{RVP("gaV$Md<1"s'^,3VzMt!UerneqT5UPٶe;uȲ,eukQA1=ҿ$WX]b㬮Ӂr, Ѿ'!ƑF\G|FyAӧp][Hiy7QÏ }w$~Dh/17dE>:0HxHn@ؓ#軠*֞5x -1pa?k Bu&ֽPan\3YSҴKCk6nIbF kcx 1J|{,)]0bΌ-eNpxkԟ0^T CQw -k7.YBy܅rzA} fק͈hl3de|"嫢ߓqb邉 >U+RYZZU[ާ ?Y'JtC5B:uޏ:TE{k(w'Ao wa6>>Op'z}QV>FF9\^<)epl|/0K6den M0=ώzӬAږ!cGysN^ya5/CVFdCפL Bx .{mWH/1Ec 'J7ͼYtLc,$C(Y(( P3)5 wc)KaN!*ʩUQMס^ϧ|%[.$aڪMZk%]C=1(};ȃj!z?Ro[?Ja}~R( =T'˾ [P}y+߁}}ww܏u+=s' T/~~d#~#5+&8N%>a8R#㪶UH}>v0|mXj=(o6 \<P5L-=!1Du뚧OịI=@GdۣS#'Kx#Ῥó!z"YT ~iIDATuȝm:ZC<@XO&!Itinָ_8z~H08/_NɉepB>grZ 2Փb82Fai< UTGu#\fX\m9.kO||Xw<6ƃ5FĦLj>5Nz $?=vVH9mmAz&mȡC&5{TPVz 6aM1:`N[s214`$BPE9AO۬yB!?NP-TiPٺB6sTmAX!ckTUN2˟|>kik7~H}ɗ4n,!|_ڂ*SW`ɧrܽ`um &x^xT͛t/j3V;هtecJA ${LߕUQ`PqEȔ)Ӱ:ѪMsCqή`lS:LDC!s ȊIP4n #dQ,D & ѪpJ`nfdfQm TTy??K|*Dȴ39̪qjڬ}YX ` TLP\.W'_2q_r8Sq_J=MsJ|,Ot1{,ێ~ >J'E]c`:-m'Y;W4(_Q Ok"ׯ 2􂧰!V+<&Z]DŽb}t2U8a Uj('y*z:|jejVxPYP-jP!/:qHȸU% Oh 8+l*[a6j_U/I-:jA*{(:A{Pbr˵5yᡜfPȤT',eg}k}ʞ1je9dQƧ9(qT"7T~~K}Gy@u4CY)6ѣrƝF'Fe('AL O8umC´_'L7:HCpf02!;zJttbR˄/Ta3oU&flQN-pL*vN@3_T!2c0LXǍj4N`"uO>ۄ}o`Ȥ#oDYϽj%`nOo__3>wvmM9>c ^kSz6~+gZ.Wuϫ:6w^\PP5|gz.Ow&;3S_&˼]&?;3Q8 ߙZe_~we^U 1\xQPP4RUQ5DAAAAPEAAAAAAAAAAPEAAAAAAAAysAٳg)**********뮏gY`1/nϿ7AEEEEEEEEP}ug05 ܯM>G/kFF[_{YW~dهg:)Nn< d"Y/d_/8 9e}__ @xC͞c<<rP_V"2E&SRP-2RV>qD\*jKзR}[Aė5Y*׿"{Iٻ_T/%Z 9gNxS;봎uHE{vR1^W%2{ `;CJS>ܼyFSGMLm1+ KPlgLLi\ʱ 8MYn 7]?2OBCdQO cuDU'|Viy78S% .짿Zglُ4{a؀u/T}nޮ{ sn=bڽQނL&[x Yv4E%#FwUI0c+ٗG= z @AbDsJ昡u$P*3apߓQƀ&EʆbPڂ\j.gTmoˬNU }'rNH%E*wB*]N˻ԣ݇*51I| Ѿ5^c?x][okEu_xZ9Lqo)ы^迷`r@I]!sClܞ+;_eW`jeTu_[^^7l[pܺBz ?4ߴYu YK pMB" 'nrv2B lq {؂npNJFXOc憄Ʉ}718 byi&p0մ]2RSf|fGdu9&mΛj'7X})U2+ k84܉z@@>&4}ȩvZX#Ae*s8alAFߺn tyu|ΟP!}O!vl:_k|?o6Gus6WucQvD6 ^a0f{=X\{=X"-0i|aa;ބp߹Ow/0K"U~6" =)晍9^3l9? +䘧n]dp[s fӡMU9̃z 4,}s\>&eDbIn,\2'feb `QV><%*fV1&K2;L4ȌR0̖29HEC l2:l[9(*. \VQ.B>TizͶdrKuTQOGu2Z$mUw(/&Ie1E̠*KzQ$E*KT)eW>hQQfxH\gz&:4g t &P5A%ځc6<$8z;0pF3*걂5CֲdTT} ^l7Ǣ0ڵU5^w8m)!0̌[B}u9Cܓ!=m×Fx d/Fm㋠z͓X}y'L>GN"X@E=%` 2nWa=%^yhUr02[c.XfIljsAsTDR2z8 >bBxPYPU2"uP782xW#m%AUي2' 8NA 4\Hs>3t&Ie/ϗP-eX]n6!u12V1K7GTC@db˹5y FP[aEA\u Tb_O3ssTU9H'hZ?eM7? U:agt7 OI$Z33ࡨva3B m3Ļ v.h1b/%۲1~BJ}seJjV6=v3 7<#9R)5ޫ,sh9RjVjxKCs,=;&wuϝ)пiX.׵*ה=&]X.}Sʫte = hGPETl53d{F+68ڌ!qҩ8f$TeFM0*U6WnwMÙ5y^ns&CwF:p6 9yc3G}pgCmB4xB[9 m08&PmI4'`xOG]TTOүRwDd~O4@[}uYWNX'ZXn~_oŚ׃{WWk2wZ,3nWy^X3rJenrJL_joؿtHlZpI#- jM|>Z&rJ Ac2t.ca ?Ⱥ9GcOg\  +ƺlݥϟ׼o=2 jG{5}4| BTTX:nA3r!\kWN_5i?e/7I^C BPEP( BTQ( BP/;ho677a.H$~BxTߎQHUUJw~?$I(xT߼Q/jXuQ VK(444444447ub,o3}}vR" K')G/HoDWV.**WhJ5-"n(kR]C~^SG.|7Ql& A w_sVq,D}&r aqY)C?Z>!d_k)3;Īۏ1 1ZYL$}7TwtL&QL"EP$h4ҟpSH4`}܇ZrQѡ!!!!&-8f|V;Xފ2Xg6EAʬv>־Np&UڔV&jE;3J sPԈ)SYI|$ %ȮR[lKICކȱ/HtKc+.MЯ HTRG)Ehh-jՏOgSO*WZ}z;֭  tn1jֵuWܮ&VC ՠJ"eŃMptTF}w6E-h ESEwW-9} :pLoxvE/>Ӵ[vvA)\t黉#p52݅ G }HǨ5O}.'dE815( eq 3o}`u~rڮ d3fk4(r=9liS) ? >I#א}2e `PԨDc*_2de Tʲ=CNM +aQoҶG?aq r_LxIECvqqC~^1{^sZuJAx/? +^V@5A©Nsaq%i].&AL15zNKP5.ZF$gݾk1u پmXP1j.^X°VץîS[x==Xwr67ƼhZT)s%#Aje!&iC$MHe!7k3ttlbuX [zn)8ļ*>ʲdyw7=1+Y9KAVVB  md1[ͼ,[[q9$e!ʆp"/S23!}T?2ޟ$:fTS{Ty5igj7?0CAu"f>al<9a`M1Ez]B5zYW+)٧״,{0"iY!i=/J4y9p @7mC\:<#GE>Ab"Kڪ4AEC^13*9OelMU/VjQv =Пy[2[Uj/w3T)lu 4oN`vl#Oᢋ\K ӚNa'[ bU;[kmK >W*{p@/w8hU?}Պиg(RfrVweMT.)\z"9M&·tCu?We!SI5y:3HT<6rWNkAu s!AT2G̛F̀/ ld $ W+@RdF> Vpg|'Dԍ~q(&o}XcO6MI )i]b]'St>)E٩ƶLc@-oȃju?W2h!|/jbTm =p2ge~?`kEPEkTg-m za*u0:lt#eqmzz2U[G,l /lئZJVvaVi_@ЦUj_*gxS JrB /TxǬju5Lzg hF+h1 T8#ʼn 8<1cXK:TuvV&ٰL#yR # PT߇'MB/f/3'9F}o!ֶ<>>hnUEK#X{)SqBrNE3Q{r]u5Ԅ%eCE3zV AQ&T,&:fTю OWTn5g}j>!F .PUc}Rܢ5mjAU?z6:[ϻR ı2nzfZ@HVj l7:|UƇ6G!罏,ZgaQ-A7 Q;~[7 ,†T>4%yP,wFA5_,BVmx| k*5#PUʄ#c23O dLRV&9d0.eYYC [LNmc]Tp·U AU=T~mܚx(ڌpIcXI@çM)ӆW,qǨLUHh5&rY~@BQ>`F!*C𾝁mQ7kϓӹs3gg9ؠZ`,Q:21T+GrS4Ίt2UjOvC๯醾6ãU:8}83`u#xqn;rrrOkY!1kp< %< {/\Lz}͚fOΏeju)2NPi385?:X̳KNG}1%cmbdjl7j S2i4 \OːZ9wC dUz#% 6T$пzcYM}vni:1{~#%Pu'^k;̣x@s?[.0:_uki\,8}V,7訬+joؓ=jyR6[.4"J5PM\T5[! /HWjP.ĭDg&U4OÚy68^_B)vu> ^󄆆或]ԼvGe$ǟ_TDP}6C;M1~KA~)_ 볢D,X J$\_u  BP(G0~s(u}Wx%# BP(շ/pm|1G:~dݵK{Iڍ$\#I~>.ziX^M~g(TS-''' Juyw(GHӠ}?WS~g܆>PŽyQZ/G6 zejYqs6FaYc srqp֝'s;ó!x1ت$)PZD}H"gG+#EPmqR;!(Og7;ADyTP:_-y H&M!ihBK:WAdU$gr q9% SZ~C@$Pwrv6$%($6IJNb4;'Daqc66yeu lh&*CU60OLRfBɣ=@~sﷄTRίG5uit(!d'ds0OW՝e>ܸq8s] Kz!_OtJ9kp粶p, TvA7 @}@&Wl%|KwnϹM`vsngekCY'cXO EǓ xIķV98ٮI&6&ͱKx<ސYÓ, "!݊7٢s~s|gs8vUԇNŭoqFdBZjK̺nY/a*?5'+a=h/.,Ao_<t~JDtCC)Įr^> *4h D4U97)=?v3HOƧL'C.E8L9.oO\8=PuZƽ`qlSL2aj}BzAgh]_2^?TX TG57t%ףD]!_Ӽt=XFz:Ė@ǡVk B3˷$+`tPN9E^PpFDI¸8-,Ad$P:xj[Y6Ob1kAu6x*yZ'< ){Bt/h&6~ I;-ڢoU>P&Z3ǨZvGG>YĪV'PZO_—} d5)_eXwCs|çU{M]Gui꬚L'o ΑF}y%gi%Q"{[xCʱ= ǮjyE!0Gg+a! Jc4_Nڣaj דM i cIau C062Ŕ0TO$cJ==viM !'Po!F[4v?oCkG"ߖ߀k/vZ TTG @*)/50ep@? TIg݆M6WXly'Os7{ \Ϝwsz,_rAzgvpjQj*(y8 0Ja+mři@Rm[p$׼:n:3bpWpuc8 J igIx&)撬,6 %X6f38l( W]/ 9M2c1&/5^T-&2mP̑%KibN:,ia[ţjJܕM Pi8&6oC0Ջm."5ȻMuVM'S5͸ T_!qh=_kj[am,ڃ ~t@%t Yӫ嚇~Bz\}h~ 4z#$˿Ɨ@{3kΎLd5lv䝆vFۡPČR[;S>L<\&kQ,$ɖG*I@uBN\kL)O(>;=1 +{O WD=yBp~.xV{+l[z,lv,( LRq P(V66@P(* BP( BP( zAACPECCCCCCCCCP)pQ ':~ xjTߦ]}`wI>Rœ싹$\_̧3N=ߥYw$^vc! WȺyϥ~b-r TUbnW"iy#Y*{p^$M=ICC{oE2YA,XÇ*NbAײ ]"|v-+Y ׿/OO{#y-o!qΞ= Kl K #V! |)~!$mȟ9n,_ J'ޮ+4c$SE) #v~Or+̐6T#%V &a3igԡ+p\$dH#3Ō Jg\Ns*bS@{kj&6ݬ$X"#ZF Uշa_58P*spy{sJ@SMi*`>ѥA믧#ǹ/ԉX  6cѣu()6dk<fR'>FSP*@ppK'Nh dU(P:¼1 6cM(E( LhM`;iu^ʐg?%+,P氬ffǨAue(* XZ9 Pd sǭ~fC|}^2iz4bˇ:(gA;^o Tud@uT3.M#䓬,NjڣZ ζu7]Iz¶vtu@ ZZGyU]O&lN߂>'i:Iv5僮6X orڠ턶jUYm]0-Rԛ-i'PҾu^I]]#k Z УϳZH=mûC;EPDT18@(Gaax"}QvZ ) ȕy%æ SRan6nx3SLL}fԴ-~(p~'YK6(8#hrf}O&,ު1tfiY jQE{zM2VF$PT2$k顪tH_ɶv?BCP=):j8/ :?_%P! Y!bW9v|yU5 z  K/ )ǀUWKqM,Z"nÒ݃f], KBs>Hicem X[%H 1ᆠ ~lnoyz Bb͎ӽ.lzY}^6Sa觠cZ*N' Ax&i]n7 NO}["!ʺ~hfXaDpp0a{G%dJ&ux`%`f&PPlVi :btdZwfPUS~QHĂlx1k|Ú#>HIJ8Ԍj(ʤ<祔eg@)(}e( # J<ڇF;4<SX 6)-uM6L*/ԊJA{T4DS0> AؠJt:AQ_pQX-O W#ܳJuZT'.:-x 8amps6UrT$LalV"`tp*ۖzD\o~RӃ{a*l|@¦F)VG'ڼl8/^ =|6Xm~h 7ǣ2CqX& 2mqAs4=v JkbX=kA}MHltgr#`uE?>lIP@LD+6dw!: ț6é\Kld2ɰOI$JMYP˧w_QY l#`Z,-Vd"=6v?BCP= NLLn>_x!,_%Ft}MT`KA5.ص/?y0 6:tml%PAU@U=T'ܝy 7AԂy{yP!wCWm^=F] ŗ\]5jQN: CQꡘ7z>ipsY7Rh @ޗ6Bh]&kԘ &@>꽹/TڕŅFXj|ܛ2P'zB&;fYOA $/!2 }=u3ڨOף2pR v@Ug&PgV-AX;VGhա!H&G.ן/#/'z ktS˨:O-l&OkCw}TAuʣXm&PmXN=42þJ}6gب=ģjخvZjUw~]U`6[v*Vڰ~hk:~`OJ@U`2&PX8cP`^=5k]u3 5Őor6C5!C~ӱ4?/)rrwH_ӣNݑI6:BQة}:nn<5= 6OZ( TTO T?ǞwOUTS_j`4S\@ %VlymC%mmwd<Tpasc6)xzAlNZZ"BCHMo[,l'yU-&oZdaP= hծ n.A%5{T|2Mv$;䩘*]v-@ݕfC;P-j^J㢃[,2&AQU jLIY2\T1YUYۨP3JAQef HFڑ_QmvEP4`Jw427?>IIF1em洔TU&VةZ\a<y | Z[Jb> 3գt|TǍ-JxM#u.gAoxF$*r5xS k^444޶JFPY&MT,V(N[س X>?>n|~N'g>¶?߿0O.BND{G:+6wx*T?d ?~,oVE{4444U4444444444U44444444T_x( BP(Ի&U BP(OQK(Wpe~z{r+6VaN y 2<;1ɰ ~DȲ Jn#i@eYxϗD'Vv@: A- I҉T U'E 'k_ɺOW}}DXKx8s;pho.|/+c^`~9^/׫\7ocJOS"p9g'Fh lB;v <sTn!mY%-w΂R94x6cG#!-;)MSSF`t v bŹv?^kx8G)z~`nI B2xxEv8FlNGA-믿@rܱʸ<=t9vS% )4h^uҠ߷p?ͅ3pNΟ9!9 5w("8X'2OEznu w;- s" 9H ^V#w6~v$e(qP:yc~A;䬃kdKBNV׺clxiVFi4Q sX$cA i|"z,λJ[nAUzN nlo1?G Tր0*+F`d2$2¯go8ivT4r\Ŏ\ƯG5uit(!d'ds0OW{|.P@${.1^\9=ʼ\:#b-t_k{pLuY.CP6*)r}xݠ^/ބ1a˔ʆo]axCx-)"QY'/ܻ|;fI]S7:9s2MJL4`L'PCla>L*<+<;cuSP G0S 7::k$ \ұcṯ㳚y0P5*NiӹЇbWF 줍Y^5'[TO "W(rB{r7Uݫ.X5>^9ÚS ƹ^{PfA t14Bs(*Bp` u8YG.Hzfw̋:5XhZӑҜk-xG P=_c- 0|ݣx"~wG(c*տ__pQX-O W#ܳJuZwWmWGA؄{{$9p?Bgol~ 4 Te(k|Vy f 6}P`X 9I@!/vlX{1(Ү>+P-Hk{n0jQ7G:kw|{`CڪtV}漃Uq ʎ)ţBnTaV{Q!ϴcz+I&$ȅӻ8bT59MrOPZTs|v<"vJG3g܏P'qxC1PC(Y% Jt ](uփ94 F*KgLxX ΪXw^{iZ[& :Aqa C1Fo|' qPAhj˽tŠTͷ`vXO` TwҮ7׊x SCbw)MZA<gM^թ: ī<6oRaRNT?TqOq]kUOף&עL[6x۩IɳI1 g#qAupp=\K%7uO/ƺZ>& Y4XG0:;;{P) !U[^Ĩ*:P=Uj$ Y63sбs* V{7`V=55ڮYjP:Do<+i!}QFx5mQbSj&@EhsXxz¦<SpǑsTQꭋQf~ȓRQȷ7ڠ6ZɃ "Y7QE5=p N>5,x[+^ הpV7excH/}Vs&PP_yxT,,[頺脪@ZkAUѼE7lrD( ~UYdސrs)`.qY,mzmGMfrx!)H&Cdv:~ z8i`K_Y{x n\}oTf#Auj1[̑@YJk|ACچOucT6.i˒wΥTTOGG2 ߈BE}{Jtj K\|h]iW=t 7 w4^OUG}w/v;Fڹ L Lz]݁x͠^d*㦾pm@{x<4u**c56#c:ַUmⴏA7iٙcT"0l.2[xI0 [ڪdÒoQh}PyW 'S5͸ T^!ʶ8u*j9Y ^)\eB[9kR&FvMJgܦwۇgya#W\^ICW;Ag|D' ?7.{OҷNߑh9{vd[(z*kLj.E@+S6>ޣL)eԇx/ZAeӜT(BP}Ǟe]ٽ'u+sƞU.cVhҠMAH Ç/T~=eIBE Z.eW!_&i>Q+_;lpl?80E6ϜhH9򥊍;|;L&![4: SIVV` iC` hG|#_9h1[Xˆ1EA7R)?Ts ʿ ^g#m~&60MM|`\XO/[A(+<=t9vS% )4h^uҠ.>O=%fl }IfR'ۅ@V?YJ$+<A!u\,CQI(qZ/Yj&(0x,cxvgZVQ$ Rt:4'YGZX}g&Ǥwj6 Az()!˟uygD)tQoQ{,mӷ ;y[^^x?ͽ"twmZZ[`C v=)^~nAPׅZb Zm@zzVna8mԵ x:y9D]cimOk#H٩t9{C+uy2&C;I+xCQNhъs|Q=;`t{&zM`٤,.T`L4ÀVV mjb&P&u0uj ;)5#wȟ1$W6Y?FOK8(R`C5Kw`RI,|JGNr_qQ5UfIht~ea?C1i;+c܏T"W(rB{r7Uݫ ^opwYzzh/zX~a8ƋNS_qj<-4+-bbE5(c[HM,(;/QuƾZ -n*sV64"h@N̏dr +50PɘNzmgj+7'hxJFJ@>6 1O>a|TljE %Bj ڴDpxx< ADJt:AQ_pQX-O W#ܳJuZTLT`H U)*!@\;VcziU]eئK1wCMœL!iN*Wm<2o ƪm}=}K$cl%ZWœ2HcMSŀwP |D=9rP=PEn O e"@?`xqQ*J ;Du\XTm쓠TZ ܓkY@It9զpIrtP-^P_4 y% Q{]l~f' `bf"F{u^b#L}Jj}JM[2TJD l43{}kO>p?BCP=!NLLn :QD׈iU,]wa=&0YNU:,`4baKO6O 7M gӻ T2x :gRoR~*z+:(k(蠪`ITk@u5i||hfCQꡘ7z>i{ ' ޛdq8jʰr_|ySczTC,.3ɕ ]֣q&:AVНT{T%k/a͌z2}Qx}6v=~CW58G#d@+(*i TY ٢}Y3ǨZv-ޏT CCCL&\?i ?} _F^BO%D躧|ݗQc -uwV3džשa<Ug6T{tYG=`K` v06Ͻ9]mTf06$ُv٤&fq@ͼPsKǡOg pGA蠚(b?5O/!kb5ލ:m)\ <$F,{4ZUhMG{2IgnX U<:_&]Rp.FuZ&[c|hh'`.F_KjE>%hs|'^)UU:gSF28jן? ^`KTHY*Iv[U=~3 ǶNo<ˈ-Y]R4d@%W !m$_[؄n I!)myL*?N9u\]8[=}vIXޥ0,x~E3~hPI2P**؏Q-˚7br]$ dY5,Y$ UYh IZ(P*@IqHPU|ٕj*o.F:c2D'FH MYYdXa3,yKz:@A_;,^5/LB P[, cTldEPɽ> aG淲e BD ^O@P%We<5g;=XZ ;M:.Vh5 * MQU~v}}љ.[[3pۇg1 {ʣ6j2,B&;6[;8v{o[g?я7u}MpŽEGƐcdT@zSU0![*źwg4阀l0XT \WuEm _eF1 vc]Mh ܵ 6 s~9fR?,'s}‡gׯSSxfZGڬ?jU>| A-W\^ICW;Ag|D' ?7.SUHĉŻ@ل[ޤ.-15]eVUYBBl?5lw3'ajAAx &]7de::f`UCu֭!WTTkWmU>ΉoIL`%%ȌKJz/7 !֟>,C2|<=_}4~ !Yﭰm/ORmcNgS$N 5aߩv1P=3^83M *!*|j+m¦Ӂ,}?0U ,-Rbj/BP( zׄBP( BP)j|W|̯Cw:YοzBP( AuLDKN9:+| BP(sI6 OAgpzTK wI%$BΑu$?MK cꭨYAYH$+P8J9iGz P-lA<ՠ-+A)yBP} zHt2~=eIBE Z.eW!_&i>Q+fY7̙3p|/naFEy&84%wIs6'Gn:@i!Fh l4L1G%}f,lNҜ *DfmlCXH6c)2 v>cWA?%*)gU}\7ѥA믧(spY>v{F։ BSd]# Q;B>ɺOD]aѯk'7po*{[}pk] A/\w.<: v.y ZsMչ3p'Rpڷps^pom8;&\_c<~/]Sxm@& [-~&PU6ޑ0%+@9l0^-̀XphEA/lTI fI]Sq <8]2Nag{X_ m S,o[X[^g.݃.~ ?&嬲rz!o|߬7<\$ \Uy/ _"uAt Mt)^^=kOT0uV a qA,$7 0A=iwP6B A!9kD>i`OҬRma&gwOA(Y`sC"-9Ӑ̱O 47Ώ@ Ph^1^!009N? I`9ܱN9Ap` u8Y.HCqrrߊ0@oX'wC|.ƵB $BV~Bc  5Żz,-Oh-ZSx<9vnp:&E+;xmW?VCM"$rROtlxRu7 q}8ߟnQ@(!9`²cJ`ǯrdxwS9) (B<gi9Ǯ$uY⬋Q0s143+*txDb:pfG(=57t%ףD]!_Ӽt=XC+PIAk*6'tP;;yO!m)T/MSg".T^;uu4(;U>.PI FOTsxr 'iqSo+'Ll:^17z,69& "RN#szn_5!* .l0 0 \6I"8G-cI!6C Ewb^ފlUټI O8d_iI܃o\ v6*b'K[EEc5J7}HM*vej qr?B!TQ?/K艾^|]2jN>CAu%64ܔ8d+{TSʛ>̵Phwt%+%kW;w+7)>1+1Ƃ QՈ>Gt@e\>pqOԆGNC)ӆG>js(@`xpLוb(>z6_:&\FKEXe?KUKt~[g_|aguT/<|q[ax_`=~Yy_{T??P5ֽ@O3<9*s(gbc[T_<ޜa \kn݇7F H(7W5_ eXpPrj<+ Wr[T$c|*ܱ[6>gn˥d̪8]{2CL[xUCՈD&ncpMz }6~@Ʊs ["jrW/S!YJJz4:?Jg!O o/zA At#忦[y#A븴ǥ;IqKCMPVkǖzmo bnB&nv1aDJ mg!Д>?_&Vz2u677CxPuMtUcc^/lF|T@7ǿksY.߇||Əcl"_R'E@CJJJJ+fjv t:61J}\J4HIII TIIIIIIIIII TIIIIIIIII+ $$$$$$$$$$PTߒmMmVgB Zq\Y[P um_ n 5ĪKAj*Wt?gaEF! $2> :,: i!svV酌^` s %AuowJ7 sʀjffxj ,(ru83gWꐛ:xvN!j Uh@6ڥe6lV@\FBcQ} ev/R;0h} ʝӸZr15|^[ѐ ˷SXjRfz˱(#e8l ?]i%*Q2Wc!r,) Q(Xw +[EZ$x'8)hR\7Pκ8D_IYC{ m(@uTY{}L" |ֻ2.%Bm_K / YhIoPؤ-ae~OB6*?ȭ?EE?ư|ZYhj [Zg3 unżqJM ߊƷm*(+D.rPURσzKbg ;˶1Pfb$U_^wA͈sߵ;0[Fq$*[$$Vt: nKADn" > 4 iHs(TnTO`eibpW=(4"k-L@GjDȥ۳ v>+bP!H+2H塐CrARX!i,`s%S|` =r "5j/d䠻l,(CNu2$- lokD] JF[ + XӸA+@5LHDEwzQ?W2ksPb4gPVD ͠=MRY2eaXB{vKJ򦠊Pj&ן͊/jpa:35 w~>-m.4~S8#/6iE""\ w.p+,]0^lRE `@IfxԪPMfNA8qqv\bXL$q(siGD` r%G_sJhК;"ou'v& ]ԪpGǵ)"UuA9YjxT~e>(wڃV@j{T1VT,ِ {EAz𼩊.Ep/qT.`Ku˯G:fYx[Eut2#@2f&=c`- 3 TI 0 U4v|c (.ŖC'VM6p~w_L䵭;ǝ-KB2fE#:<Xw8(tXg/@ՁV)ݗ ck G(j`2jqekK, x!2`M-%C^t':y roTѽU-s13P^*Vg޻. + oCiw 1pXuw^k.߬ A!ԩ7CS1 CUd]^*Z#аZ;L}Wm5@")Zij EGAWͦCPds mpCZxJ~j@,КHb% U3sߑ{BΗXnR,7&A`6j4k-P|Қ 8Uʳ XT/~ -f߉2k}\# ՓBs$Νv,}F?]S.XVF-2[:O5ghS}iQcrjP+JaDA2kYkKUXY5;Ǖ-x292qk-tu}߆9O[]nt9WݚKw/.4~"4x"&"`]ZNgNcw. }Y zj]xEjuOROR"oҧnSc9QxH#h&F\[c*6:-.qy! ѭOE~gdȇ;w %ac..~3jW3d]N>gDžLbv$OO9zIv8gC]@ ;|.gn0gT:uR"{)H$ܻ)<3a1r.2FZA_]:Q6i>E<͹Kpfhމ xl׀}Ga `%r.`*Rb#{ypUag 8O`R 8r(2$H岤QX@3Dzǂ=%Tz3T>5fsTZ'j jB7@IBT2~w~,Hc𡝮7N; 944Z@A! |An>̄ itjB!*khQF„XXׯOF7Aw,i3ꯀcV\6q ;rOegc%&H_*1(dg8~)w𺎣CeEu݃qB +^pX {U@2fV`E&?Ǯ &Z)"Y FNڔeoDk)[f?m7e n<*[H9Wu9?xiޯ]?Ex"~H݂ق|}b]5'ĸa.'oc hn@qta?|ߥ{CpLJLJPL`sBc،!"ۇ1d8]b4ny$wp̪jQ Og`b&ڧ *V)rQC385v0 '!5{ҁB"mf8TF؂'<Oj1:홾krs"I0$Ep FP;a{k_)Dy$pG؅3{zde9JrQKx,Ƴ l,n]5U^O'l 아9ξ17+^Z̕A1 ͘*FCkqcu.0b0@nvZ}vq>]J k S$K .8* b9S$Yx~rR6plD:jt;(a;=θ,σ8e#"s+`X'yhF:sōG.jZJ7֦r.mkvc;e~F$sLomZS`<1/Vg#Xx#LoN{g|&=}k>"pZguͮ&!U'{G=? mʐAv[:|Ix-bN(ffE.ISKJ/ﳝ2@At`vc-"#nqDyrx$KtE6QR, J/IBw+\ Aߢ3f瑼F ,9fGn |!sm{wM WBCؤX%(fW=<'@~eZ(|룑# v4 LX]fR5hKas1ǡH_- 8^8@[1O cKiتyLZ3B*ר> 5Q$AXA] Wy3t AЩA=߆?]Dޒpz'~'ڡN~㻓`>WzLR_-ttySqy%ߖZyOrK41ܺ8M]q6ˊݠ;lw;veœ.NGoߜyG]<7oA}'`0j'WXXE٬=Lmg LsNTnvcKTqWF]mݱ7w{Ã^; {$_wuIAFZ&rqevaJ 4S d" .FoqAI+O5d|\@br&u(=4fK)_b(RSCtdyavhMh,C%`vʰqb3u1] #%ʲ %?]2Pwm%FLG@}7J]#<?/3}΋brrHL4*]tUEX%d:++*c `(Xg\ 32ET5uOZJ?"! 2q~ƬWwroa.;ڭ*O^npЇpm`pAyS%۟(!o[X9pXIw -jKc=J`Oaw~~7QIa̞HLk[0(Ük 5 r%̱_. g\NIFUa ͞bg:?{>MI.`VԯQ<4XqpE+Hs~2XV bv ɽ2YR9JwRcE !5$Ј^+촻;XgW06pK.2&oϟFHe9c-2o@VaG -*XMpG^УZU\Uáke7DQ`~Vy5\]PpҍWZ8k+]~9KD"ǂ'ڃ`3dd3nQha\ZZ3|W/q[-!AQ ,;; pX{Osͽ3PK zTPK&7pL styles.xmlێ_1p}mwwg"@ "-Ѷ(P_C~[%|G] Ys$R;'D"=P` 6|5}wz9hb'١ v>,1h! dS>XYCܶ3-3sdKmgKyv_㶝o]cD/m`>&X,v20!>r,>Ha[JRVf aEӦFٵX/lLg@9p8 }OݩEwd;(P =15؄-8d@PIn(w̼7J q]-*%g4Fz^s `QwCM]8j;dfCE;U'] )!Dc n!XPpfK,/åһ-hX1G vlkL:rG ϖ5?ߌOO S^LbZS =i#-R󪴭p7CA@lg8&Q쭏oҵAvL Ǟ-G{k{ ([fm&t}$6Ut8`ɦ%Q eAI4LBU'`tfw+ݤzd؟k+l2|T &Z;A;HW]BDYm bD$*:6Ðy'ޗ&-%+9ц A}ą#ꊙ侥:tf6dtKO sϴ{ GY-C}0N A?&m#c%2{xӲTz&eg9΍✔D2e(iP%< fޣ2R Lr ѹ6Ni}xTA_bRg[P3-M{k6H=笿cl6ZPH~yIrTx"7[|+!\װyz";ݶ{2/cv v%:ιqgt"nWǫDq /mM[Ĝ<=R"]H]$WלnpWzbt1*z{X6oE9-~ZpmLհث~oZRXgjϨ^{F/Z{lۨ>IG#W2QT:)P4j\Q㗬Q`16{nԔTe#ZɭnbьIfL^fUs^f:s4cZ7f/Wu|B}b4ܪlaǼNk fR5)\ 9<'Z?%~%nV;"'PD}zwngq;懜ŧMt^¤q)W~09fYGB~M*éV V_Byl~zJNeTJTrJ6TJfT9]sP9kr*MTλ@墉EF>4 ЉCD At" 0:@c D1N"@'h GnGv'N#9!D<݉xd7#nGv'N#1~`_UVX*J+JK8iD#{e#Ffx#ҙM vl*ιFe8_ȃ$=ʟ6˃mkۘXטXkGtژާ'LâQ-Z:)7=~+oArrEҘmD+N%' 1*HHu֘e_vYVz0LR.OIcx*'Ket׍]R8Y$%ʼ|FVV=`tzZ7]5nUۗh:_l=/`_X_ֵz}a>^z@7vjn%Wo8F;;6 {VzKBZ >E#eHa^GP>spI Nz&A)Mc~|SǞt L) v\ &=V\/G4.~|ɴTD -wy?tɞcI@BC˭ؼ"IgykZ#ѩlc ZIL`Y"ye`Ҙ~W٪uΐagQΏ*"IVQ#-^Q:?/]5"qӻNj0R'3]\; !D(s 3EtTx4Z5Hpͷ:}*e_/h'ԇK>ͫ~H]gAr[ٷK:PnUN;ecdlɗ/ViV 4lABPKUGjPK&7pL settings.xmlZsH ~"4LBǐҦ%gw 4'?]Iջ\<up,8c"uz|O:\l&ch'9hwj9zĞڶAitE6ׯ?ݮW shZWhe~4F=龪꧟Bč"~^LٛV;8Y-i^\Aζ9Y]](aZ{߿ [tGNUG duQ1$-eB`Ot)0kpa%}&!QW_(p߁HFmY^#F}wE}Ok{AkoO`\o <1d0ԄE#aDȿfG 72Be?nJ 65! ݤR0Il'B=*M(J߲;ucĢJGS( ؕGX T==(D*敖uhxPS+ZZ b)\x [W >KqDaG`bj ]\xCH R7؝pYWX`*jnt&LoٌVlb fժ`1alo *, V|j4Зƺi YVTH= E=xHcS0g̭aO T(!1-؆:!%~ jJV!q-Qk*CmT ٌ#|P8A{•&Xǣ5c+I3)\Etռ'_ *w?m2]á)1}ۚɹ $*B9a<>קtXt_9g5z\>ZF:bʃ= j_x;!G|pZύ2g'ΧW39Պ%]N&,2v(3 FV =*5u?-MN$9B+YE1jI?&lAV*&X$yMEN<}3h"ESP)AON|]4Jn@7N.OG @Q#UO l p;^E{=~cuV̱Qr'E %f?PK&fPK&7pL manifest.rdf͓n0v"D4rq=h(F~ D",N/SVsUOM )'u!_;c 6٪œT:s\ܦP-k,9% &\6{@`= VRCo{vh/W4gؐ]C#dKO<7`Ӕ,uT՞cV (C'  Q.%j% ɑPݞqv,>ږ~5 <Ϸ<7ݡb* 8! ~-ϒ4ٽ E ^;pq6vvfCAN@j.vmDoo+^J+*kf/Լ`oDx_IzXPK΀9APK&7pL^2 ''mimetypePK&7pLX,,MThumbnails/thumbnail.pngPK&7pL0J ,layout-cachePK&7pL=n=n5,Pictures/1000020100000244000000B1E7B5E557054DF266.pngPK&7pL|z??5ePictures/100002010000023B000000D92929027D8B7ECDCF.pngPK&7pLll5.Pictures/10000201000002420000009B64B473936320B9B9.pngPK&7pLn]]~~5Pictures/10000201000002AA000001E147B599D77C785324.pngPK&7pL zT "content.xmlPK&7pLUGj :.styles.xmlPK&7pLp# <settings.xmlPK&7pL&fBmeta.xmlPK&7pLh "Dmanifest.rdfPK&7pLaEConfigurations2/toolpanel/PK&7pLEConfigurations2/statusbar/PK&7pLEConfigurations2/progressbar/PK&7pL FConfigurations2/toolbar/PK&7pLAFConfigurations2/popupmenu/PK&7pLyFConfigurations2/floater/PK&7pLFConfigurations2/menubar/PK&7pL΀9AFMETA-INF/manifest.xmlPKHflare-engine-1.14/distribution/macos/docs/Flare DMG Build Documentation.pdf000066400000000000000000006615761434514645000265250ustar00rootroot00000000000000%PDF-1.5 % 17 0 obj << /Linearized 1 /L 222078 /H [ 775 182 ] /O 21 /E 105128 /N 5 /T 221707 >> endobj 18 0 obj << /Type /XRef /Length 66 /Filter /FlateDecode /DecodeParms << /Columns 5 /Predictor 12 >> /W [ 1 3 1 ] /Index [ 17 27 ] /Info 29 0 R /Root 19 0 R /Size 44 /Prev 221708 /ID [<162ada9ceffc3ae9c2dd5a63d76ae007><162ada9ceffc3ae9c2dd5a63d76ae007>] >> stream xcbd`g`b``8 "HFc6b "%Ad>X&Y`] %D~ c endstream endobj 19 0 obj << /Pages 30 0 R /Type /Catalog >> endobj 20 0 obj << /Filter /FlateDecode /S 72 /Length 103 >> stream xc```b``fg`f`cd0d9p,o O įuf4Ix?`6030d/```dbr200 endstream endobj 21 0 obj << /Annots [ << /A << /S /URI /Type /Action /URI (mailto:thomas@thomasleavitt.org) >> /Border [ 0 0 0 ] /F 4 /Rect [ 204.75 663 383.25 679.5 ] /Subtype /Link /Type /Annot >> ] /Contents 23 0 R /MediaBox [ 0 0 612 792 ] /Parent 30 0 R /Resources << /ExtGState << /G0 31 0 R >> /Font << /F0 32 0 R /F1 35 0 R >> /ProcSets [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << /X0 22 0 R >> >> /Type /Page >> endobj 22 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /ColorTransform 0 /Filter /DCTDecode /Height 481 /Subtype /Image /Type /XObject /Width 682 /Length 65149 >> stream JFIFC   %# , #&')*)-0-(0%()(C   ((((((((((((((((((((((((((((((((((((((((((((((((((("S !1AQR"a2bq#3B$r%4C56STUstcd-!1AQaq"2 ?:b0PNF*s KLnKRC+* : β=|QVdts@-I~Kq Jn\8e4':K`taC<"2{wm/VҴs|a@ !a%@(䊁v)l7"SҼ 2sns!êm!lq + r@dsSnԹ7imˑ}y?6F;y?Jϟ;`3*}L+JTPFc"ކ`1hqS}/Rq¦PJ.%)#4c<$Fi=FjJJb ,J *jҶg iQEEQEEPQEEPF?+W8kL)1\)8xeWJߣ?664T[%.2GZGt9,fԋ}.5aee.HZVPsjr7Q%<16םa*[nRғx#$nl%%-"ۘZx8$)TBR>!W˺/۵LŸԷPnm4PR$.7$o{omw8mdew  fgkf{isdo8.@ @%jJrp#ÿs(((o){ūEuJ\S\E)IQ$ y ~7_vn1wRȎmNp O%c;vD֡G~HLT4Z-)@$(➋{K(8/l-!N$JA>k^)qe%8 B ;"WCv^/\T )HF{~a#Mlm[d&SKQNRNڲ])eD>InX;jQu!87 k[Dߐ((((&ljJvz"\eS=qE A*Y)~5ז3;}kFs4S=?LןNMQD֔j Q<p1qc Vjg/Q3+#*WUߨ[tww#KA])J J'b' ڙsmLEʠԓ`Wժ;1'_ da89'GZ3caCƔ$,sxW;S=?Yh4 R\-pn#q *Pq:u$8$?Rn/fzLC嵄+oe>!$Am3(ڙsFZ.SvZ`q uH@RsPGma`L2(CFu;iZ9#]LE6z"\e3ES=?L@D_jg/S4P-3(ڙs mLE6z"\e3ES=?L@D_jg/S4P-3(ڙs mLE6z"\e3ES=?L@D_jg/S4P-3+̽A[qr?WTP-3(ڙs$:׀PN>GyV!mGևV!!۟D_jg/RkZ"5 n-K*ƀ5 x+-̙N€ϑ )p8OD_jg/T_ahZӥ/&9?+Ρnk ף^(+% YpHϵS=?JFđw]8ChPVO ;ώxj"fNiAPNp|T?ʂjg/Q3);!{ejTR̶F03oއGyaj<#Am3(ڙs{Q!2ؓ˨ ֜A!@[ $:koCQ%Zu5PՍI0ؚ ?FD_HVý(谤I'^p9I;CbF;zsdgmLE6z"\e3ES=?L@D_jg/S4P-3(ڙs i I=__vz"\e3ES=?L@D_jg/S4P-3(ڙs mLE6z"\e3ES=?L@fX$䜟kD_jqvޘia)[`+$|ڙsmLEI0lv⾽D6S(d wHu/$k*?chڙsmLEʬԐfla ҭKQJN4cVcm#^KVsXoNqH.bhLmF"h꾠% eZ!o qjBHO‰{79Jǘ# YW(IRT8&7JV?C?voS4 )Rs,^xm#YRS[H)v"9.6e6dEA*#Ȕkiz6Vkiz6Vkiz6Vkiz6Vkiz6Vkiz6Vkiz6Vkiz6Vkiz6Vkiz6VY!:KW&6IQPN2yVA!hi kV}Ims-BCj(iљfs-%[-T%ZIO8'"M]͵Yy Bd:kG0qǍz=><R\FJu$dDNX6RFXiB!((IJUgH\'цv6620cViz62-1f̷ʌdAf+Q oW)W)kM=9Oĸ<ӲRPjc$cqk5@dKc%*LJIqzAQ|~fE1R[J΢ 8اLi(DJTyw;+zBHR֭)HK5p-M^IJ?R_d&8x4IJ$xHNq!l99d9%:O ZHE_o2¶ש'4d b4؏>J1 z ~%O43HKelG0,J ) q>)2J՜bBZaHPP9y-E5OH+E5OH+E5OH+E5OH+E5OH+E5OH+E5OH+E5OH+J\-Bd#mtծ=>m#ANTTMḊ x:x}{=>m#@U_B`!2 FBcHWHdBrkGl!o8 46wND 0TrÙm ʵ I%0&3̭ȋH SZ1Dv$Ci[K I`I1I.yH9?ڃ"q>ɖy$6X@<S[#GaRq(q>e9;f;u *[JҰ)8IO"ehizhizhizhizhiz訷hOoaMH#zCW{4odf'yU[ԟTվ|A9&rFoi1>Wa9ڊ.Jq =h~bRrBj-g89}>FKEE#F|T[4oKEE#F|T[4oKEE#F|T[4oKEE#F|T[4oKEE#F|@A bXnY/.$UnK|CzAPH'[=7hiFvf5d jfAt%( s?6ުr[6*Knl}(/yXRV'y7hY ),pehkJFTdkF0KD÷tS4o֫}>FZ*-7h%O}>FZ*-7h%O}>FZ*-7h%O}>FZ*-7h%.*#kQ\^~ouH}0SoѾ#@s3P!R.p<{}}>F k;u,4PV7;F~{#F|F1rT(*KʣojܒBpBs5\ɡ%鎽O1o^GȶKG9|[7hg#4$O}>F}>F hhO}>F hhO^((((gvsľkuy%D vjTRKjsJRd'āQ֓aP:?v+n`jZ'Q^[W/ !o['ېJZT WR($k%9$c5M89 (,HJ]E<[)cZ 2@RBVU|5{1uueؾ:KC5!S.)8m)) @{NM]:3[ytq/G' ⢲\kt2dE@on&ވ.- OU+b8R50p5TH>XZwyቊTE̸9~v4ٖMWͺKiH JI#'Þӷ[rn=م8.9w1immӒIHmEDsy拇H~)!R^h hYZq k}qu1gej[KJ[R_0:q ?hu+Dm|F@ed?)J hn3sf:ҡaAH$sUL&6=rKAyh1_ sSRIȈɓ|* 5r[mÇq$V?L}_7귭vgmJDQTJA$|1PK.o8-\ҲVʣ# lpOvTj*mmc9ک?!k2O: ( V JX'e]~k[e+V#U$(W.nr%űΞMJ!eㅷ(ʐ0O sxis"FҟoH 9F0V<@|аܠjt1䈰T6SZTѨ+-;ϳ~o~˔m]Z 0)e[{N1Ge Sƃ"$IMpQ-&6o!*Lc.pGs19y}FG1݌cNVyr!a))XFts@S.Dm˃ V-x!E$9 wW-1%'FKzR~PL!~c&CJШ %Jd|{UvXhun)+Z+~4q@O--r.ڌPQN'賭BʛHp pR T@C>5'Z^^e.;HRyQҟN7V1 ]`+Hm$rF;p|2i-q*ktJ\ xs)꧓}qAj +UdpK#Bߓ֩:n"wkP$mei*sLTW PcwI?AOq:eI%@Q@QEQ@QEQ@R$T5MH8RT=U9CvzKQ[mC V dxYkw-?Ij5zSX_r;l^n9!ji[d$0"7n N<Ԁr;ưqɝ@ TtSZNˉ+#)Oj-Һ%Ez qR5yRKcyDAV\Uo3SZS@xF|*KUwaoZgĜK]JUJIWڐ(Zo["$(Et k\qvUlEI}"fJP)A1%dIRuKדּ[o׶;}" oJd6I͐ʀ3WIU=)@3_l:q! %9"BQܛs `;%4B?1IWzwus /%> RtaCH3 +中Zwzy>#zjoS68\vW8,f`$cFOvztE]2Tmr.Zj)Kp qYi=}L#;{Im[1ʐ-,8!Md ^k7AiAZ1ҝ%ŀN;R/hˎk{J`9% .*Ҥ'!\;+/k퍇c <v┶ރKlI)Y]u[nȋV6~@.g:u!G){hgJ U Wޫ77#yDHu$#>⟂;z6g"F0eR9#nj AGPA\uS :{p;U<|)DPRJrucIrA'i\n8 pZwei~H,=5Di)eח* [@\*d萶2SF3ޱƮ^0]DMqY[j՝(Nu:B1q[!MBjpd#4R-˒h@smaTۥ%:2 #<6.ۊ-ۊ)J%(=GDlc4۫u *?MQmODx.]c|"㆝|)HQVǞ1y,0 )޴i3 D[ y2LhSq;g)JcUaj(Rv󬌋5nިZc6ԀI#9F_6?CNXn>",6p֦ @QA'i -mQEQEEPQEEP/!FEEoTTPK!FEEoWgْKelO'ס2\,Yfpi ILfKF3A._INGz\(m()x$k nĵ\l3+Y2Z#*@(٩3HEfvW}^BoYe"u DJ-cԥ)ԎN3i:Ce~ϾLmIC!ЅPWH9kxkٌ3vH'_w+οfP+6fveYxk%)/Z\/Cw.n+q%'P8I9#ff_Hj"e\e $+Ȟ(f1$=荄-  *Y渝ַW"TCgjzI)9SHO]mIlh*9QJqk뒶VBR;^Dܣ9+4i/ -$G)' f?hUrݭW iÅ 89;RD{LUM岯7\$ Z]#+)NgHIWnvvpe;yiaH t'b婊M'yKw(Wgej.swc u!8ŠpIx#Izԙ7i. p䯄,6Sj*"oW#d 7prš(KhnWc%V 63pTf2dm) J@B]$HIǕ}W"rZUs\isc7LfȎRy pzN.Q!QQA.Q!QQA.| $ 9Un>͂B#83LGuDuHm ce=F'3<)l۴ِb7cAuIKKSmMi%)WA6m7(W}^By w(W}^By w(Wi~z6Wi~z6Wi~z6M1T%,Dc$!]m/MQ@ S(O,L3R宫nP]@gsƂi~zҶXSmbSIu6 dW3^guu: Dwݓi ) %!IZpg Qҁ#4DM6e=nPj:>OPZ]q0 p/dTYEr\7\eqSJp-ai!$dqvKH1vP VIJrs?Z(lҚܲ:.mbԵ[Ħ+> +⒦˲/9X5+'I:{R)w[=|!0ê欲)Vr8P3^Emff7*#K i”=|g`4%iot'-'HW xE|E {:-2:x'8nޑzT7/mKhJt'tj\?۬7OaǐRRNQ=jtͦqYļ%KI* d>/).,Pꣻvb ª2#eA"KBAx:E]7l?m{̙Ij:wq)C6C}!c.d'a˶~#.8ìHZQI'<ξñheӨqBSyU=nr_h$x )ʎ`R'S.Gj]ae=Bҥ-I*PHW'VNZ"5261FgB|tʖPdBfG';5 m܌| γ6BgŹIz$L dxHK_̠Sy%#N<{`.zn% R4A $"n>ӫМ%G G:VNZC1 F\KŖc+I*5{*H\op&ۜBmIJMj RpH> YtTȻ]\FZd8PB+ 8<H§ٲBus$63DpO'⸿ɸEAv"IPP<ϵ&ir|t(tgNi^{vAyvlmT?lsLJ!bZZB% Fc5_ԗL(Tx2@9p?B|+oqom$DAOm/I덵ǟR%ցt$- Oj鞢~K ᶚ # gr^+Op@.4խ@'8'N?R\$LC\f&HdBJG8sM6rPؐ:^ˉu! BHX&C)cKJ(; '?9sȦV̨2qC ńG|*fSg+h49#>,{s1BW DWYuq ))D#$T:9z iȊu6T N9OBi~z6!Zylʖ2[sSP̮>m/MQ@>m/MQ@>m/MQ@QJkW֯Q7E)^ZG@zލjtUcSvSћHg"֯Qsuee!G:RTFP{IGfԅxM:T+WG̀⊼@FȀjTJN8 'f@r~bsvDg7!E]sYՄ$$(حqE P&pz6HeHqƖwP@KRB\I&"kUY~7phH82w a ZʈI8H1ݓz ,u  %04)**/%]¤-&r#;R7c⬙zһ% Z($(`X#EJm*̛}R6\Qqҷ5Xmz^$|=D w#)y6¾M cǃER* q)%'q%i*#*mmڎ7Ip8kCY@ėBwCN*{j?LT[[m}+툦*ⰢqIsLLƉwlqOdĭmjRRFI)yVv791aRqj#-I@Ԥd` H㜐rxQوr -N[[+2@R zލjHVuh^ ~!7 Z,gO?%REJe+eQÉ+OɭƵzލjZL\KBPS":ȏʆFA$ysub6`>3pKk{l!8rxIW֯QԛeNCْ߳vkt higYJF4gIjH;ƶ71A:ҽG )N<昵غDNl8H#mjBE7^3:5~kW5~kW֯Qѭ^nSZGFzށ)Mj?zh-rVּgNFqޑXZKiS*CR IR}IdcLJ!†ZAZ@]z>W|3A*jH 5Q,<[,`Ij*sxt>2ZcG8Z B\(+BjZGNH@r-t!*bRo+ Js<8-ߊ#R0Tuy^RR뎡u-iX?Ζfb-+~0 y }Oo zV$;hmoo !@ISWvfd{A Ԉ{oj Q<⚹]cCM-a ZbC9FIJq4=Zضrv~d##<y]j?z [_MɍWߍn:CqNZ'kKJkW֯Q7UK7oL f;6aisPA{>=}j'r1mmuĥņ,X'$n6dFXqa(e!; cW@#?Lx5rU'ڮ!MD)7@&=.(Rx 9hf]6<& &Ŭ5v.-NĖgNuhVqΣbbzCMEqpr0JBSW)dt5 u<48P!+='}gW൛ DQœVp|ZGFzޔK r]C%8RЍ_\dSR?z5~ JkW֯Q7E)^ZG@zލj4TڍA>c{PAEOqs雝˭͎GIoL~c$#e m!n)$Ve_S)FQAiRyH*umT\ R+Q ȟ5 wyVۘH4Fdjy[&JevHZե*)вgc{QX3=Yk<ӡ =*}A*q7:+YJl%n%!kʔܓ/P췮]> SL5ʎ! h-IΑj>umt!i'"t6 H'0n^6$K݆Pe%Ig WKsWΧ~EKnɊRNl6^ U^Ӧ'K$Zݶ9DӥMipV~]8sS9S˄ &6yAĺN0Bcpkܶ?7ڬk4%Auh͉79}0c^B)=<:vl!=QnNIQ#%zږ! Kn08;ڍR,= զX::\& Ý ?; mKih/N`y:p`nml̿p~,y(Xd8ۍE)Wl0=c{QT~^M?\mRRi&wiZ!%-+N08 T?|Ԫ}FkTTڍA>c{PAEOQS~oj6?7TڍAMԍ㋌RQ^(LIqN2aCFr@c{Py`€%ė&ޛ,7J;1IT,3!*weY*:R~Ê~oj6?7nĔ8%\ ʕGReLNjN섔9-tߎ4*ᶷTyТw0RZLF1l΀1YQ9 R4/F~lxթ8Ҕ7c:Jx+AQ:||QԛyKƕE:t52RH34xg5\1%`[a;Ȏ*IҠj-Fr,X/JӮ Z +u$}k숅ep `%dNysZmF-y1qv$kB'}i܇e;1ќt<6<9Ws[MFLyrUiIC\.HwӲ"[ҜޔeA r[FX#XϊIa) )Il!'ȒZc{Q l~oj(?7ڂ *}Fc{Q l~oj(?7ڂ RPSQ\gw#1V[ڍAT.j M(rB=Zzl~oj*")vSͩ3I -#c{Pb/t`]ڛuJ Z֠VrR8X"çy0DĤ)ԧsZxD^l XK6\r:թ)HՏ÷,p`b.4aM%$?HQӊ~oj6?7">c{PAEOQS~oj6?7TڍA>c{PME|֟Pѭ>E|֟Pѭ>E|֟Pѭ>E|֟Pѭ>N덶jWq5SL%KDZ:6jRTN ;RͦKzq#VP=&YJl(8 q97ׯcIbnҖ $ !$_TJ< Vnн7B]mn XHR[$|`+3k nڙ~ľsnDyqLrJ]n$~ Z.3.55m2Ew0ObcrF_ĴNH=(Rq7D#|@enBmƿyI-s:Q9;ǞwEѲæYCL2\zs$%J:Q))Q&\uL*)SԸq!l6FUGcz龘tt ۳}HwMDGH|-,cԐjmi$)Sѕ,gV!ۣ2Ϯ; w{Z5`x|_RYnIbR+C-0R*!8'' ##av4![qnIqN%U)$'wZΣmKdn,aj/FʹMͭYwTe [Ay@IbӺZvJAHRGiB?Nn\|,jO*MV)]i~^{ cv .5X+V WF AV9 `f$CT@K/m:T4(iO)F{ AB\$`eY?sVM]_5>kO}+Z}CFރZ}CFރZ}CFރZ}CFރZ}CFނ\[=;tu%i8)!':/3ͅ R.q~1ĨhlRMneĸG XHkJql R;3%RPJ=@DrC.%)Qpr Go3V=5*}̹*\a Ig'־c=3:/JTBNt~#,Ch!&U=0 v*cԔO<ڎ#$ AGnDReBcqH[I̫ۜk.TI{JJS;?out@9ɉR`Ґ)~RE3"͙&=:KNGթ $~m:G&92BD)J|0; vyV+dYi8U (Wtjʁ$Ӫ_ Ujd$ )#J1v 3k,!3۹]UMa -]ud ạ:͊m AyE,*'H<ƻ7C+^)uHԌJg!u4/k VCZgȑRnCbB|m/k:vw3W4r U-Zqu3~Z B@CGTWizE|֟Pѭ>E|֟Pѭ>U=Re #t9N(5~m9Ƿ5k>Ƌ>#f%’N3v?Z 0V\zLVҹ*PyTgI#œUu‘19Y;BN?7E֘R"!ANRֲ1VN sDž}M [Rԡ(Y9#-nλ`SaQq!IGV22\]1&kβU$x sZklQj+ZZTI?)k0Ja<jJ ' Epe,=zJI-BywjkQz+%hl(d#խ S2pqnI(NUcd1rkԅ<ҖR)97σfClnjKm%֜ u:3.~szZ,\ Է Vd`Ftǎ3VzzhjYε|լk 89<ʴZzLxWoL9F`߂h1%̉1d9)n0![iKdnw8 Ռ@|1֦܆qГˆQ8=qZP2C6Q+(< R=sCJ%Z}k'mZʔO䯋z!0BBPn I>Jc}SY[aA+m% ^{qn 64<8YW̮;sRˍbZLa>b0֝ﹱnNBE}:T1pqPIvl,yOͩќ 4G:)[lce⤭XuA*R t< %":K F|ͨsI''OMwSkOΦR~}z#LČ RY$SkO}RZ}CFޢ_5>kO}>_5>kO}( (( (KB)P((Tiq)J |Ng<߷"(KJqĶ($ 9="&*sOf"ݜCB=%܎!@%)Wc6c>nvȇ H<>Ew?NDW7͖ɵ߻Ƣ3` Pʼ_}BOe ٌڕOZqiO|Qս,Yyn*S.-ǣ0Y?/$qޤ٨z4eFo[o0Bkԕ؂8"G޿=V mr+ZlCS.4 QlA-Է$u HuqqոT*)؊ɳeȇ %%C-n/Nʀ%LfLw"#V(I ʳ `׌1Ge3oSu*ěbQmR vBtm Z𽡹I zn2\_ŷt6A'cqy,6u\HZB%i# 8 ieuQ2q^pصؙM!;oS%r N%@hdm?i-F,puin{tRT1хxj2ks=Fkj^/z i%mHPRNApfɛBj~s=6d qHݖP7X R+(V T~,Ò䍸Cǔt$t466;HѴOwEHѴOwEHѴOwEHѴOwEHѴOwEHѴOwE.Yin<R`$\8Fu0qsۜ㜊SbktC궹eXw??$l'>8 V~/4%HѴOaX8i xyrYQД+ BrAO'a_Ȏ&Te2\S)h>\ 8ۅhXB^Gң& ҹ!N 5Q`L+D G[i2M| ;g­[1!XCaX֥BTtFrT̶jqАГL6Mhq܆Х'NJƲUDc.KO[ul0P ;#OKzo>ƙeŬ||‰VmMT6Td: \G޼7kr~%\_1X*R3@U^W@eGq1Vg R\X֥mF5H"~m<.3R\Ɔ V{`6y9R`Fu-Nd+Q 082|ꗩ _dSv^g')43[! ^jζf.\dW x4YU\R۱I&[elݿc*Ɯq9v#f5܀yj UdBRX)Cg<>KWo謧DjwL9B %d~s<YQpttIJxG޼ۤK:d&dYn4kK-Y98Gzʲ8Gޗ"_b;d yOI;!'¡/N۝TU!we8ȩ%9+a Kh* Q8kIiTZ)M:Fs9')j>LC3i\ fN ¾re#qBmz=>ˍSgD:Pqk7R gqR.#-Ka] 1Ui L BI78Lu\Ti,mV2I?ڐRTI \&LOS>Ƣ2۔eRVNpPBʲGq[yy3f!:mQO4rABSt5+jEGHu0\eD}R(S¡ӕD*iz6訮6iz6izhOc}>F^7hOc}>F\v[;Q>te 'ї(qA+@)%'ȠPDq8#؟ --)3C@ܯP aвJƏ1@KmB!#:I d(NH:'Q^[W/ !o['ېJZT WR($k%9$L&V"_O}>F2l_KJz]%䡇Mʜ lRx=&ܮ-Ѻ8ӎ ZPTqZ3biݬO}>F2۳טf[6_6I- 4)$ Vzou$ vb"%%%iF? $NU5{ =y>F׮Zomqi j['-KBT@*:2@ T1z9ȉIq3E)ϐƢOp;zN|5oڴHQ-"$pܝjd)-FR`qb$D̊{pj և %擃w_y7kSfW™Rx\Z[(%'RRxU-ui 'Q?b[erS(-q[I`һݖ7hOIu8LY_!j 7f(MLoѾ#KQLoѾ#K@|4 oѾ#K@|4 oѾ#K@|5QKC QZpBIWB.e8Fc)qO}>F16 qM$RRRp|s f$-4-uI P춒TžluPjmtӜry H-ZGQ]ue#Ce SB\iP#3_{5jEzD\@ IS$B $F&m^ql^ 8 )VyƒM#F|dۼHl3 O$!Je`qrO`yOR(qې_\qyڃg#F|br!5=ۋ)C2KhIW.c%g 3n -xvʔS”23RJԄDԃDv *'atLIwɄ?)EimuraqigKA# {ֵTU11YEM(0g"6D[kz؈ Ρ+}I Q(8 aIҡ^\%>eŹjK-$u'9 Vb. Bc덳nm7(Vc*“ Gg#mMngy6w2JG|Z)g,i߀[jjîFm%0c!JW8$g$jvhJvhJvhJv Q: mM8r '>wC!Cr8'?T q 2o {LfR_qaGd.?(%I8)Y?ʁ&zyJeK(m$!텬rx+|F@6im%G$14 q)ɱgyN!_KǾ@r#rd8FNsozȿnu2 Seh~FOt:Sǎ:.D[c,N I,ٕ%J/uI8dRMt%>C42)܄NpN~omۂR( 4wX.]nD!. ?t͹In2J} @RөQ$ڹEڑ+dkFuܥ!NpAEEЬGJZ]q1pR?:0BH<0@*,tWl}lȖS!.%@rF^UDh bĎ˅ke BRU$[]y 7)7!e[9x` xս/!FRoP1E/!FTW{KѴOEPKv2qI)8<>_ލ}1v9r\PJRC (Î1Na|&s3͇ϩض豧KO:cTiLkȏa@qFJ[m y)+bl2iV$%Y\9>un4w d)MR\Kae?G8򯏦/ʏ5uŚ _u94œx|4lJӐq9 Ŵu7UHOUoTg%`B #5r@ ܎IE\n:6[B$'5צ7vjh58i2CAv#A7WOmwVp;uŔLWeH@HI)VR޽k&,1 l%IK@ N;vN.sA}--c`qH-Ìxb;Im$v[PuEwOKQ]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F> aL- V0,dsy"<eTzp3:@m/Kma0Fˊ*[jBJTI$xCJ2,].f7mZ_}hi: #N~sbؼKahB){ 7_^8NxH'[,܈W ETfPҚIBOs_u-%tE281y ~Kl,rKҤ)Ɋl’N\}A?*p28A='W,YSr,4i!pHHā54\i2yP3ړ|-9`~LńntZ3UHQvܜER!,IS $p&iDa.2PH>+6d"L(48ҥ'Vs .{~2KK[!ZGBUe&t8Y(TIA{+vmۍhP)RU=<9. g\#qHgC~q3:Z !Zv*ji9S\-s:#"s,`~n88yW;:ZBT8 ӺAiчq]멬ӑR?U29g OW$jq$$ (*S56>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>Q]/F>R 2#!4fpRН?_{i~z6DJM |ޔ=OW{KѴOf~!:$l(_8?5_޸r uMBjւdyȩRIjqlm*UʒgEZP~{0t׺J<}|n&J6P3_^\KYq.!aHI >`da=|CQRCaXO` v66dM]RR[Sa!ZIJUq6hi1rIQ;RǷ34#eJP@$;@R,i~z6_ލ}8_ލ}8_ލ}8_ލ}( (( (#};fzprNHq[{j$Gpn!?S|uŽp;a'<<{_,m/4ɈZg^7_!CQ #4ɼ~=`p8XG ;'~tܛ#JeiyyG~Qv': eL( R{ ]g28')ƭ9xݨ2VGf&se>"9Bc₰G 'NF3C* iU16-%iH`(vr2f}\ҧDQE\R[N1_+HHS?gLM_0)iS :* d}!@?QSN} -8K(ZR )k:l6()lT#": 4drMYl@*(((((((((HxM|q%I7 =~c\6AJ*YQEQb_ÁfܔM\֜- **Jsˆ(h& E쥸T67PPV299-vȇ-䨼)pCQ8O~+vS.\{Mquljmaa F@|ƭuAZPk# B# 3t(xLmNs21eUo薷|K|1HbJTW x(':u47Ֆ#%a$)o*CVFχju$bDi nӫO͜4CTI,.ys ^tXTVq([ "GԂkb[he:J Vs{)a jCNl!iRBU3?IA&kD f(ܴq``wOwv^ˌ<+Wʐ9_L;S]\X:G#iF>ަ ųS2};p #8縩>44ӑeZĎpAa2x )VrpjZG^9?%:owdu|kW6pu*~7|]*~3-ա$JN &c15ZGFz޹~kW(:֯Qѭ^hZGFz޹j?z~kW(:֯Qѭ^hZGFz޹j?z~kW(:֯Qѭ^hTŌ2 ֮N'J~pb4ikmJO #+motͦ%)HQ)8U J.ܛ|IL; _k -) Ju H'Êe^ZG^g 10dl31*1LlHq%cRu,e#{馢6_Ja/ rP| ~"pԅIA*#I#iR;wmVwE K[1)X:Rdω=]5fh2K!Kp4+:UuH|qGӸZ_  hWQ[ovk6ieHRU)#V~Rȋ=b1<ʣ-!D7 E q*p\I4EwLRR΍A$dg$3֮y^N&DS{#k^z#qVIt!IY)# VG[ %5reND]ν`a$nw=&nBJRYK̡+RP25|!J"1z/ܒiҷIJ?Ih-#;"Kl[.˷:|"ښHl:~ʴItLF 2륽?I>괉qb\Vd1 ) $R|yuVjRx-[ ~zi Y}:ai.08FrAʪnE .<LČej $X}Hm}䵢+!e6դP |'2.!8RNhJiǏVt麾X`|:ua['_8|qi wvc۞>g@C|$cyI̥CTw6*8{o3qܨoX^tdg+aQt/m!\ZNaQBx% !;튑I\W֯Q4Pu^ZG\AֵzލjsEZ?z5~kWyӚr%:[iNrI'y)C-4VA.6J2)"sf mZHR@$9 RH*)>H,n0SolAƨђպ[39%ZP _SԍS}ݶJRyԚ?zn~h!HU2ԓPx 0k#i".-lO̐'Wqmj?zijļ%n;hShRV#ym)M]kW;Ջc!َ<I:AjlWJ8#$Ib+4On5pdo\mm#ֵzް=MKңyQ[!*N(LJ53p[ѥݭRx\`5`֙Wqi$-Hu4-¥'9H''LĚq*wmYƴ)>Մf>fyqb$HPHbpt@gָ}R8 ƳVvq\$\Db8%,r|{qc^9iO-` -JT{w[ l ))Ҝp0yiO^ZG\Q]kW֯Q4Pu^ZG\AֵzލjsEڍSQAc{T՟eXUAi:JǸ<z QJ-Eao>Chqk9;z@r}QqBx YumJjh[=^|\si6R"K y$qv⬘u-w.-K?u^Q~n) +JFڎ]_|Ň,7Ѷ?7ڼuzeAejBES!]k ZGy $RMS6`-bxf %BRi;Mi  ά0kfmy쐮\ZppKtd|jc{V޷[v#EĩF}MƟ9i  (JFy:H^CmuWۚ:ƞAG&/mFWUuxn n{--ZBBOf^zuREP,).6-N8*!n )!EXC"}>`6;ڍ^qi=r-J-Jְ!CeAJo#:$*yԷ+㒙iTT~3jA$\1.MFc{Qj(!l~ojv?7ڦFc{Qj(!l~ojv?7ڦFc{Qj(!l~ojK)ZBV0T?B9U~&N8i QIW$%=iVc{Q6&R-̷:uzXl )9;<`:t;*mq &LSn@SqjwZ6ˊ/ B# *-(FT ud7,$;I9Ѭ<1y6%GSktl:N;%Di6"띏F%p)S4Vqx3vMqNۢڕqS% VCi?1͌1L"fc{UPOʍu R1 8΂psq?[nK9ՕpTH8w~oj6?7QuԹ1%FrRYy,A6 iAn[UwTD4R<$¾F/N:n \HQn-/I:@OUlh!l~ojv?7ڦFc{Qj(!l~ojv?7ڦFc{Qڦ8!b6,@HTڦ.3ۀ)e:pvH8c{Qd]\E M X,Xldgc;ڍSVwu ڐіCY9@/6?7ڳ͸6V[*e¥/yXR#N$PIrI;9HeKm)Ic{Q!nӳ]2J㍠lZTx'7 w&db;/.#RRRH#!}o.dl~oj6?7ME;ڍSQAc{TPC5~oj6?7MEizN5>`$~ Xe 2H)JpnVǢP,@)PPUp8(39Uퟧs)qK%D%NJJ{3J]9qg[=҄Gڴ0$ْi.,!=5≉6Gca~]i6\mF+Q!AQ)1Ʃ5σWa{ׇt`(*Jb,< 2+Ry*PIC(S\Ye 4-**?z`丘F[qh [aZaJ!Ud[ME&4$O)\lIa]RQfkn+C}+J\rֱ*H!_' k;ߓ{Yy77g|S) j}2JVVR $j~?O)Zq+[hhN>BKQJFN09zoz2;/d>~.*hO3*/[Pγs3508TNV@I<FR{z5>[]FF%?K!suMɏ1h EdIuKihD8Iў5'#Ee>Z}CXn:߸-ѡ;ZUੳn#i;KRh_ϔr(izN5>kO}:(֟Pѭ>sZ}CFޓizN5>kO}:(֟Pѭ>sZ}CFޓiWi?qhÊ A@%N;iu>T-z?SQv~q[uQ:8HH@'4ۓЂ G3IŽʕnGnJ-! rWgkO}:(֟Pѭ>sZ}CFޓizN5>kO}:(֟Pѭ>sZ}CFޓizN5>kO}:(֟Pcƒ|%Ja΢4gQUe02$=%Rv%*VIP?t[jB hI@RSj{H>]ַ[k78?.2pF*0lv⾽D6S(d wHu/$k*?chMҘF W1PkO}:(֟Pѭ>sZ}CFޓGލz}Gލz}Gލz}3WΝxTFVT1b{?uwMm#F=>.ef48)6qU69HѴOv\<1#'qts+EV!NhHF{X@ܠ:/[ַⷝZWt~c۱_GޒX}nA-HԒrt?ZcSuwxՂ0F>랟$ͽ4x(mQv<96(r5%n_ﳯri~  撴7M` Dpq+` 8BB9r,u^IZK:Sk~Ty\hb&łs sp.tٺ%F*Jk;*xo\KMKs BA*Rf̷ݠ6!q·^+wD-޾MaxLic0bkRKm2U#񚨙EJEq:у ^+SjR@kѦ߬0.-]Dg!QprsS.kDTI{6hH}Ωԛ2b$n- pk@JN|9]Q?ơ!6RfZ*@5!C=28e8RGGޑd}L)-%IJ QA=x%IIy)Z\^#$*S[HѴOS[HѴOS[HѴOS[HѴOS[HѴOS[HѴOS[HѴOr1 %qRZ91 Jqp" x̭ CM eKZ#M-kjQCOgx6kiha.jyn)- *NFy9=zjmZ!P+^>q']-ln zAqPI%N `㑫$ZK=)e?n/JGMA߁ߌ}FYz mOlK,[e'89'BұP6Du3\ZҒ+ \iL%6*J^ЊZ--KLYRTQHRH9RT̆*)KM2l'UljoCDA)o2!Aug9VfGލz}hizhizhizhizheh#̚([lv2*OqCSDrS3" Np;ko4D- `ǐ KNAȂ(!.!2wpFjHi *^$ /z tZj% d8S)Ճޞizjjc!fHu:2 cGzGޡHl ^h3Rlœ6a!?2j A'#Β JaLg[kpV!|c59L4Nd!m EZ VoRG^ :E)}QS)S--IHl#Ny8,GL!4PN)|Q9F hhO}>F hhORE$m?l5*$%h:I+4MU(o:HmTtOcMrc9w*@KjI 'S "nbPa&VcT[ G(Q 9U7_gFGHMu vT pF~T3Mo>) :N^eKK+~jT!!H?@ơy=\e M[rXcem3Tk*; 'l*O<{n|4徟*ZEtKkQm²Sk -$FTRwQMj4[IYRSge#J@PO}>FzL6~}zOPFvp-鑮IaGwW̔)]a{F)r\PTB7FtQ>5 vF|YPÓ➌:qj4k}˪isFN PH$ӥݜ#t7LM J;䁞#F|IWÕ}t]wstԙkL#K*BA qk/-qs.[[}CNVKJӔ)@0=bXP0 I)#r?QUv%1L礼KrB'%(j8H*'V4oW0i.9?PO_) qU[J<8LKEE#F|A-|4QoѾ#A-|4QoѾ#A"P!@{X#ۖKˉw_*޵rT }>FQn~YY>"BZP] J4r2\ͤwܖ͆+mʒۛ2_mJ1ËV0I?yF+!eH yK,7?+AZґ%Խu8z;;G.%:T)IR ց[;4!%Ԅ)eXHH' Ei9ML6-ݙ";W%k,np最=ߚ$cNF}>F hhO}>F hhO,uHT&[#9 x)7h0v4z 2 <$<"*NDߌ DJ\5MjLHSqhm$z AV}>FIǖ(3}ҮTu0TbKEINR^rA[;3%s\!J*pi|+>|@Q#A+ ݹI Q\ҵ$<$wR\7ySpq{L&Lstx~konšejRV(%DOMo#cٜzȴ-Z+aIO涵3Ѿ#M(j:`F MJڐB ` sW[4F$~α&UTe]A ji -/r@I瑓cI3&-OeH[(KiS$'Z}XH\[m̛"dȒ m RxƠLIM9M~Z-Ӻ H8<>\[n$ IJ5_mB( }:RHN5u)r&,ću-=,2[?(?#G=pjԉ)z2YLy*l!R^հ vM,O%i. &C oUc´Yʞ)--8̠%%xj8m7jEQoѾ#QRQoѾ#A-|4 EQ@QEN{ݖ"‹k%@<}i03bQH. d*yOFu#[D~u! Rِ~UcKi ˒<|QVs.) Il*>G?ګ?OY?_c T~w幌\lXDW=% Vx%g'RqԚE^dĎ]*ʷ*$jaרUH\tݽ_nHJ\uҽ)Vʕ0"ǀn)l]'%h?/yߊyb9}Os8xXcy}|&lK3٠Δq7J+O~2E-zϱڶ-|yy%K)}(txA!h s5e#-Β؝rjeqR˨%9]Аtn*gu Uu`$V2@b(+~X|.=Cz e 8Ba0ޮơq[ ںM\ݹ\<j:eₔB@Ԭi+:Br((((((((((Kku0@}eϸEBvS1LR[m$GR%$kjw#*Pۄq1 hhMC!շ=n2P$xh>^TęM4!9*P qޭMejZ[RVŕ+VRS4Tia 5.k@2R8(JO<*V+m/*CL_ HX_nNEKwgCD5z•%3IdT1lejjҀXT @c=nJ2du-)T`r;w4Khط#2kcggV˸[1-# p#Qyd#1ƭٙ&;? mFDRRR%\wJCJBZ΂s悷G"Sݸ=.>r?Vqq bS9p\6CHhkw%.w@'QI5辋M.L0ϏƼ/V܃?1h '#ul6e% )*q A澇#?q/3l?yr݄TC xiωmWQEo}ʉ$}l̩e$%)q$=un% ވ2Ė!%I+ Vyj٭)Ň\rJ(+?J"ipj: rBՀpNIJHpb3rmvʜt/Oڰ_)vkn+y q[ 4?yJңbWԖVfnп s H J<'3!C4˝Afj˽%RPXVp~Iיڇ%WVe?ڛKm%HH^@ZTN@眏T|ݥOZѮ9BVRRIXJu`pM}ŵn0ܸ4k]Bx4^Ϳ3jKbki!ĴZSiR>/B^uv3ȸ2|ga T@ $UM—hٮ7.6xv;4[x[:BR*(VA U_=I[ga/ݧĂʕ.Iy-$3{3T\b &BNBd$Zu13]$Ȇkz:im$ *jZBB#ɨ$o41d;._N15!j#!O8]zצݹv!wڐqD3'k}[gvmTJm}%C9PÃ޼'K$?jzff .kq8g $ʼΙQUG)A@Zڒʳ`Kz}^m؋%\Ng r9Ǎ(DT߭)E152G8^u)OnYRbqӨB7ԍEgx#G+gbRnpa-or&[i mTur1rk5Z֏YJ H#qzzsQfQS,$yUjbFc!ETQEPQEEPAp0$Kx(juA#$2q}E-5jZH( E}3s=Av+xB2ɬޜ8f0ШL; #i]#2[φQ̤Ns_bM1 \I,HJZp,3+&ͮzǞRѰ) #pg' u e^C)8x$pN|ioN? )juFH$w ?r|5q8BBN)DdY'ő>|mpwZSlD+' dMnӊm,$j>d845%"a1[7%!%8 @)99;2z X+~C2n)ˈ)IRNsU["Abez(DiQ,moO߳jUmG+N% 9HF{>}VW.bTYO"2ZJJYJrA= A\V)˩.cBJs9PT9bIp@V`9MqrS݅< El+Jk'0|*n,+d]]p)_q\Zr<:]ŻgCNSN4HR{GƫOgCIRq'K;S9ŵlXR!hZQE$O"{ҝ[ee$!# $B-t̹zuc^sN3r!C==/r6,>|59KhUuETQEPQEEPQEEPQJ\a1KseD)9&9FU:hDS5 j`md BsEʣ!r %EX'D a'Ɵ^rUm,8tBAv;q[zEqE)DG}7H-LfunaeX;ߏˬo\pӯ) *Ւ\<;==qee2~Z֜?#>ZqAz!&CIa }N'l)VB|TCl-S@s>S{uf?C74LfZ $g(իig~DE.ZZޓH 8H d!%((((((W}^By w(W}^By w(W%==t DWH#Y'5o}/P~y ϳ_J6Є:<1b;GWo)xZ>w>[־LEڵ(~N I$y 9(gO w[Ϛ+| nSLuBCKsJBJ( 8\p)IZyUQLppq_+J)J O^ga]c;:sq[l $8ICpfLԬ%{(vM.ffKM+RҥAӨ'w]Z=}^BIύx\EL=jYrk1RQ"FRR8Gƭ7Tq(D;$&-G]>GQ!Xozڹ缽d5##N@ڕE8JTRGnqP^ WOca’[^ )lUx>G?y iR.7j*B}xuʼS3]f Ou杒P NJ y 7+o:qם5.SkH 22@ycZ8iDZhqrT#RZ NBH Dy2i(Lqҭ;h<4Nk" LYu)NFTh=#}^By Ƽd~^q(IVW8'8U[."-0܌iV>Hys=}^By s\FuD9lȘ}n _Mz4}^By w(W}^By w(W}^By w(W}^By I10քPXoQ!U)SPɶT/Ҟ35g w(W5B- |;2uNHyZPᯂph5[Hr;ƩRIjqlm*UʒgEZP~{0t׺J<}|G}W>3LGuDuHm ce=F'3<)l۴ِb7cAuIKKSmMi%)WA6m7(W}^By w(W}^By w(Wi~z6Wi~z6Wi~z6Wi~z6QqˈR(`<} _.6[i [ -ԥ]JIU/KLy>?_޳CF!%! .SZrd4K,9j'p<^ T|x k_fpPR% hui+lk(#*e)%G' ( 5B.e *KĆ YI soj6i~zOߢuҢ=FR\iH9 I"0|U7nH΅uq jKѴOfoLDu"KnHa(P.mY'#*i~`ZT :p۪ 9eCJrl/F>{-_W2y"h`|;/+Nj ZF@ jk=es\4IRGHHPI#6hvi~zħ-OĺOilH2,DS)%y !@:BHբo*`sC)m$Τ3֦&M*m/F>KѴO5EKѴO5EKѴO5EnimVJP=쬸WJuP8l܌1An<420Ni:gWL52Zi )B@ )m acan)q-IIQ8vk[*4yб=5$Lە2mMXiX 884imą' F6讠v `,[  QS~t톾QYoS$3AϞxCT-Y DCZHJP5*-ۇb! я{U4Kd@n#2L?t)*ʊP2 NNp1ޙj6CGp':ֵJmK@<< eYːAƗm*PlR˷5}C =M!p \c"PI@VUcƯo7tZөȓ@BZlܒHd)LTb `+i! u$xQAu, ڕy3/ra"+ ȑƕg9qs3+wE11Uyq#GH:ϖx_ޔg&3qA cmxp<)[-)? } FWFu3\ ^-r-ْTF589)<`c#ArH\rmL->XPJmjAUu;Kl;GdV?"摌gjr1"H@ԹiQ 4{EKQR֗JG̡(FҦ_޳vej_ލ}(_ލ}(_ލ}(_ލ}(_ލ}(_ލ}(_ލ}ť (_ލ}}n#YNy_޸r uMBjւdyjnZلݔ BG' @\yBfbBPrkKz+.%Ԅ,) !`v ABcu)mN5rrI+ԬDE,\d+;١"TY $!GH{KpЏI)@BH UzMrLi (F [Jg%G9sSԱr+LĚAZp1y‚i~z6Wi~z6Wi~z6Wi~z6Wi~z6(5~kW֯Qѭ^nSZGFzށ)Mj?zqDtY@*8Ͻ#;n3 #t)?ÊbDaǞpVxd 6 #~ū͖ٷJY@ *N.:lS|F*RZ: 62:T- ;Yy `IQ<:9klZ1 [a!AĤe`t\su+QL(ʖf3S8a(H$ r̻[lNju6^#7lL/Jp,j'*'8Ucۦ[CLDײQ`R:;c5|Q=zޛ^綿Sp-`6vt[Th $dMh/:ɐ`[SRNGj?zwiF+}\H\kݑY;M>Uj/91J:j.XJ7HK䁶 W|6zލjBnVdA+M SEm }!$k}yuI9˷I~v4\mgoen+W~sیcƯ~kW&>K߄˃p)8S%BP6$8^YvҶqZj?zߊk')Mj?znSZGFzށ)Mj?z5~kW>b~ˇo[Z:Ǐzҭ=&RR9KD B~Z)1c;"C p)ixzNh'ViFa:T|EX!2l7U(iS@Dž^Z!ć%xƬ gkW֯Q-c`Li~DRqw",1 2u*{tɑouM2Oozt\OGur{ hf-rilH)!:Rr|H6Cmhe [( ڑ|?sa;(q-Ҷ֕a:ՓΣmTfe%ActW7N<ӱ[\,JmJ 2kRC) `FHү R\/Р;%ptY c+mx}RV$(+<~#M rn6Zq{(9GD䔴MJP DHK\nŤusЧw:R ΥdzlQNߌ{$h=򓧞H<2csti }(-Y+ (E@gTue3\Cp4Еx;=>.+JTwi4#Tڔ86<%HF?m8湋pfR䡇*;NF`s=nT7/:V23AR?z5~ JkW֯Q7E)^ZG@zލjtR?z5~ JkW֯Q7KMzR#JNRl,Ȯu~kW>"%bJ t2=)SZGFzށFo݀@98~,]W;ыkmc%.,6)eJ8 |zzff"EL$ sʑWJ! E#´X*qVcHSg C^i)Z<g4R:]Qf3"K0)ql-ou4Pt\R=9'ΙbbzCMEqpr0JBSWf5~kW֯Qѭ^nSZGFzށ)Mj?z5~kW9l~oj(?7ڂ *}Fc{Q*o-ŦV8 JHVk:jh;ڨ$}cN,Aq޺^f8f94R,"t+(U*]4 fҜa Q>I9Ee}?o0v 5s1ǜ3rsF(1\J͞DGvjj (orJRQ) H 8W:1hǏgzJ:& GvjC%y +yǸ~oj6?7rE} } 0bӲXL{s&ڡiPj* IDe'RJ8PlFʖ\nCa:eQIVی3g[ھ*0RJTAI Z({TeظG`# IN;~]ڦ?O]\GHZIq14ӍGVs^nĶBjbha Bxgc{Rw1%Ӳ%_lf|X݆G£y'Kx ȪөCZd$$R$ Vc{Pc0gCDBa H}I-z3ylUlU3[vuʔI (ZG~{ڍAnJLct6!5u9\~pwi&=aKkct7Y<F;ǚl~oj6?7]j<}ūa!q^OC餸3禴)ò9iLO6 S$(6 r#ƴ{ڍAA=wjm c-()kZYK`tcwn?%lTRiJrz.c{Py\6b ,sCeVv3#V>P{bYdwĸu4ζ@E!G?N+{ԋtTڍA>c{PAEOQS~oj6?7TڍA5Z}CFރZ}CFރZ}CFރZ}CFރZ}CFރR\4CA8sZ}CUxZC WG@O΂{{RYRZŽҐSG[wH FBUo떕) e ?푞œ(tq! Ֆ+'#)u,q&? ) r4/B?*9w~;[[K3™5!J$iYmxr9W4+ TkkDȄbmIO QأmF+}hB'/r +Z9񊯋5ȩĶS! RJ $#M{ߣ~^:ynl-((Jtz-٬J%t4 ^2rFOzHbKb+RVӮ)-tXRu9)=3eaT@mJ}k$ApeDddcy,g#:=%"&p[Il$)p>`_뮫ttC, ABW uAE\]ed!:tRfDsa LיCCOԒ%CFqVg{ތ{ZQ_5>kO}*E|֟Pѭ>E|֟Pѭ>E|֟Pѭ>E|֟Pѭ>E|֟Pѭ>E|֟Pѭ>E|֟Pѭ>WNejmEqIZN HIβ7Kabreq*!Z)ԃ[m1.+alV20FE(뒜[+!j 'TP"/;q˹IJTr\ ՗L˟>ˇ%r23rݎKD>)R !93)#QZ@B8` i;ci_Y75R$rk-%(*ÀF|35o C6սn4үqiAP IV3jeaNRp@9M Yf\,.(mꋡ(@)I_ZY/t6΂ PIpTm܊RqIQIqd)%D"-[KN(dU7FuDu\O[ph[n*R|¸߮NBñ\`8B A@ԣۏ׊'X퓤 З@@W K-$ڐ) C]gܬ%.O*JoiCYmX'<.h|q&sFe8K<@8)lZИ)p(\$E8hJ%ԥOGմ'3Ѕw\wm#R |Dʱd\g`ё#]\bʏ~$Zu(i.ZJO3H?d=+ mHDTm[yhRh+?ga!d:aРUL&v.|*BW5N `1õlL&mH{[I8|9f1deǒX“)7-LLly ~V0ǁV$ksUn}݆Ïhz5>hz5>j)hu΢;$%!Z֟Pѭ>`y9}w,jeX1-90!K-:p4ryh>: Rsu*cKcNrR"%2a3BҖpR"*c35 M iSm='p{.3aCe6mmrVPx{ >憔K8Nڵ#( _r3Ba 󞄄*5D}~X9r%ƒ2VJ> stream x\n6}߯s2( ؛l[h&@h@"%]VvN\8{!H;QO~_ݿw.w~ɝN3Vy=}:=}91t{֜~<0)R|nsCP>%c칁`ɹRyAm IyVU9>7=4D}L< ʇQDډrW- 8qĦbQ%ꆰ>R&MiZ5yP#IC d t(ܦz\҇]wɪ蒉svw>ϿO;|H1wȤk:Mpn\ g1SLRd-v"6^h|`#S\,4>,33ˮ !BQG 5FwKM]i%jo3ӱl0=#2>Y2*237pz̙ <-樹h:,.g=S, 2X+7Y9O]|O3 2iu!MagaTn^t+.崰`M,8V 0Rj8K B= 􀓏 @ X e^b:6 1qԵ%Hp:-an6;NX*Ζ󊄬St`3,bnJ7 _k>tt$ljYnK]S! oMa[Z(k0t<!ʎG՞+kM`h\(,שT NrL)h /tZ#x>VU oe?!6j". ToPW a쑤 c(.A\,Jn݁tSFۿ..zC}A\Qg7$'j0i cw'ǞaA 7\;ZUAρ2lP(<6\A eˆ[)=dҒnq  NtEBi|Z6P~FŎ$V:#8NBsVb'1ľ'DVo"eL*bM$Ռ >5$Bh6)'pjQ|*m*q,~3JK{@QL.CM #h+AlLgˊ@ Vnzc - ڝDۑǂ ^zh@1G=1GJ;z>BN uy&qc|QSR@5H) 3nB6[&N\IA| 6cic!9cKnAxk `7a$Fg%"sd F :XQp|Z絹T}.jZxZ^)bMR>txI ppPIJ W@ YX!\D%\݂CX;dI&ǤƊEkLiqP8B PL/5_ }F\1^H *ŮXttHc70)l;] 6zZN }hY? e!GbTxWjIer|Bt8'f^1;Zژ렸2mYV((K`vyŢ<̺x 28qڎe 3ҜC*.FW*#ҥh/K`7&w uWT CecX|KDo> stream xZ t\y߽3,l ,< )>ByʇdUfֻ&" oXUh4Mўj@9ϧ(/&.ċh냔-ZRjSgy ܟɯdnjtF}s` 3` :σ$|}!}|vEC;ﷀhUQuέT|J:WӐmPfm y'[~&uJJ):O\ok>şs:c!}f݅wgg /wc$T\Fi vNw$j1Kw+g)d}XQ>ֹ;6} 'l}^vs9=pЮ{x_S{v>t^zRڹZk۵#2;ȟy3׳ijSily|Pib8["Q%jE\/.9s3g4򰱗I/f:6M{4333߳s޳ƳitzX8]B( +&@إ݆P$z.Ux쟏}{E Kcpz߈~37 W⫡WyWvl\ Di2um% ^d|:/3Yl^/3g||/ y|1_—e.gW f!fv%al;_l'<|bװku zv"nd&v3ʾv/=/mvvž¾ʾ+Bv7go-6o{{ #({ώl%0;R<<>|^=O">eb}5o먓(@&]NWPzp4  {=o{#o>MM7_vMcu5WU_t+ϯZ3-XX>ws3KM): R^nřFuvan\ WA"[mBܺ} YϪVӦֺڒ6Eʩr.aJ&}뗶Z\5e5[km1B6rom ek7g h{VW !8}hPrf!3iz'_YԁS,Q\fvr -r?oRյ;zVXX;|y׍omVHZoQ%va4lj*kw50[#fMHYu/Q7^k;(uy7~[*JRt 2niџ̪nCڼFȚwʔE%pe乳D++mZ z\WbRWVlpHlgYyY@d9k6th3j8hO&sD,gCzƠFdEMkxۼV̵fMZU.id/Ϭ9X+k$Y"1hc|RZ}j`+([v R.lEĪ*jhUR?mn]4ô:654(-tJ QzUeftNmUހ]v.N/嬬ipYhX,Цдޞ3Vn*kgj=Ze&ɆT#0~).a4nUo޾/_e>2ֶ͚Npo^mVZkܼ֚tI>Md~mw6®X]nSk]Һ_;S.^_}4ֶ_Kt_jf-([`AL~&,?_a'~ ]QMY\?t<1@c {/=*5i> np7w}^G FWq%K]u숯>Sp N)8SnNrizuOrQF.,]Ejs-R6eVٔ~NNm_́h5D'gW88T,7RdSvI0m<}^@Z{Vrœӵ|2-}R5 Oܷ\yowpNy!weizޣEϣy?uI-糖k.ڕzf:dj?E/;&z *Ī`PDͮ@<&F̈D\67]];"hֻPX(pl51(*V,DZ7|@"bM80QkF _<W.6v_GEsv3&1B3O|Q3_b] 2z %d~\ h& +͐! E@a߈]!K+!-tE TUVVt;1vpw,7E/kFM~́pnoXXq^k\'V]n}݆@R{3d2h8bD;DSLX"˰:vᄔ{(N vbF;&"?C2 ]Q6ރvȌB2>_z eQWpxd1]bĈߌţfG"p72OA0bZNPFl"ѣzGP4f' JԘx*bgveEtEP\#euߔǪ\vRq.hv2HQ,!ȇN>]6OWjZU_*mސ$:& !iצ[!!;іe;GC;ĕ #&Ȯ@!'hzܱ@8c1ޑzʇC D*2A-5_l`ha3n|9yrPcr*-l7DL22 L1c&8mOzwR+3B}P6ؐ)ĨD Np`EafoI &b?'+*7:[a99ʰF>EiF HB,kb9Guw3 lQXOnv! ءO!R".g !~a 02>pB}H%OASMAlfC^RZ pNjsq:#ӣd'Cp|gpPש2r0,q5j(:j(;28+H&?NJq4#33=D7̄TlǢ[XVnjЮHט9xSz}v:܇P]WO}iUޝFj TҎdDKH=E2+2*;sqFS"; #g~9]BjFs26FVF]1{^#Nl7~P~jT_YYJun2 ɮRƾ?uԞ*x?or'6DqH볧pj5i}+mgk)C+A;PtƶН7ULy59ߤzǫw) {JW)3W$@_ nG9ӻa2Y9 qZz';j8SiǸAu~sz$xܩ$L>:1g/VEskS&d9IEݙs>%w򩏫S#9D4ӡe$bjZԙ?gfuy#=cgOVPѝߙ~_TƲGwSȒ;S;TBS&PJW!ΛXNj3Ƙ=OKuLjJn1tK\ut̛BTf?Bwah팏I8kUqfݶЇoO?mi~]Imgendstream endobj 25 0 obj << /Filter /FlateDecode /Length 223 >> stream x]Aj0 E>3I!N)dii8(")T`'@q&<95llF=m:rD5TwQ;\+;dO3>7.H *upI/&̂ v>a{DhJ_E64j+gN!A2\ܵz,=sPvc> stream xw|T7~f=ɦmf !"YJ(BOHMDP, l%  ", b"$ϙ|~?{3̙sΜw7@@ @ڈ&)@#?$t6vEc&>7o3FkG~ǎ6DUxǃVc12Ѽ7 c'>ȉ%^I#/v =2Y),@zpQo19bî)_b~|tuC9U?[NLt=~&X0T&k?& 0D`TtدTjBᣟlnXxXd1 ;` ^ Lź;0i5o ."rQa~2-8$ ckc0[װM 1;xlFb,y`z Ƈ`P\\"Yo&b%/q.x{|ݱN,![)|DY.1P 5"&ȦV84?(8o18'hL1v\Aelwb&d@) hDz &@_+ pdr>]@FB/Lg?4@=f*` (5P.k2OP'B4疹:Tpd}~mE# /=Ѧl!*<u$$As1 p>XGÐkk9gx0s' <ËH5r:)wRyoAޠ8 8n#vet1ç`pHCUHz /+*K`""8GԂ?&5|6bL ٳd{ <-aX6P"?qX1|=,yD Q11D^ ;(F>hΡfY/ra{*gY,^CxSo ve9BH+vVbFZɆ[[!FPAn9|nb ?(8sar:_";wt;Ӑ&0slXB%NC{w.HO` ɐx#n~<OGY#%sg;ߥix+Q,#LXM C^2vGZLMFms4O.#x]VpȺ0<7|>iX;e#^>!퇴}mM1mɕ;i=ԝcwoŭ &n'Σ~OhKR/۟c܌yc>PƧb E_yBmU }/v@ψHbGC1=q?`DW!!mCXs)Ee3?B:3L; - A>+|}3N 6 P>ܗ?|~X@W;sK<"#FVP4ԭ3XZ2$O-GΓ-q@AP .aP&,3͆) {=*N/%~/Pъ8EE_D"Z ݊g::MrHGj.IR)Gj'u&IsW3wz͝;W;xE1oOO?,~L.ԭsVNp7utproq.wWOO?sx=<Şў Wūm-jiT۪6]mNk kg.]]^[.P'԰Yf"!A}*@# gXv?[ְl+{UOU'?8TxȹRfI'IKFΥK!΍G=q9sj9q8~9\~#V7w4rrS\F΍JdΑ묖 k[#缵k;Y[Vxm]ssQ0W}]H FQ"$ djfL2O'M0: ftxBhWEN]4Ynm+u[znn݂e|]SK ҂K~Øz vR٥_N8K_7m\Xsae^m/F\r- w>w>|V3ΧO:><9ӹ]=wWչw:9^<\޹ڟK8yu8ꯨ/Mo%s gkǕ[pNNө0.iu{ M ia$Z.y=^/+F:Rv?bt݇sTiw&-q[Ra,5 ,a<tʐ)? x8`#_[xށ]0F* '`>| _p>Wa O!|Ka< 0A ` LP!*<=( s#'k30"B-ԑdYOzQH6-BDCDG&N^ /dNv6GrRRFJ#*'T#13%+9@ ' r&o#$a7H$"o$ĐX owO Iēc]r '{h3'.@CNȿȇ#1z I"p3p.g9 |Agܫ~!&A$RKH=񓦸%R2*RURUS iFTG@DBè46NRIvIh4ADS''-H:u @CiDi ]B&L BaTX!V p|Q.v =^ap\8/pEN ?ӟ/WzFoOuF :]N"{̶l^apW|l+ǝekgոOd7!vɎQ6{c;c4}>b3,w;Ͼ`Ev}ɾb_ }ˮ#a?k:~e7okrd?؟/V {[x:J8 mQo/ABOoa6:A|$'_s翸pxw;w}ǻ]i&uVfˌi͛5MINjq'R#6&:*nY&Aj*Bd%4չXy}ڵOaư[2}fuO*Ibwkzk9͚Jy.wK"`|E'W䫑=*9ǸӉ 6җߧ SYجtW1En|>܍4I{.[^e)Æas 1$ұ`BY}ēee%>:yXXݹ3vؽх>L9%5l|1.MTpGEy.ATTֿF uc3*"R%͚1c0":QerLc6r@sj̓QlDkB|#qELmx>o#U9B9 7Q.' q_J/9#)ltzu&$$>G+lw:/pLJ .ojJ %y %R>|*O7ţ\ x۽m`yƲPֱ@R!y@cnSB=JBsg* 5Nب*pBI=mӕ 8`.+V[CP3W8ܚ0EuPYmCBplִ3.sYqٰ@pdrG葲y S^뼼y5iF!!0LEF EDlB(z2΂79'T5;[UtDxis0$mPg:Lgr3sxy`:x9c< 1xzUtk qY'>\z0ra`a~dzoJRr\i5)=HJҭt)CJRz?)M!1AJi(%ے^;)=AJwR!nR@J%孢n2ɓIE{WHin1:Nk'! O#rʋ`Hɹt6woa÷pނ [xF sCkWʡTD.b(b.B!¤wK 7OѷIXS)UXCp,|Yeھu{5}X\U!ظ*s <RGCH[C΄-!D^3=M㪉gb(FDb< qҸU*9"H%qNUa9싛%nB\0*Xp Ƹq]~byKrc mťRdlRܩ!p@Vm\,PVR+*8e2ZiUYT&ASiT*BTT*kU7?*L `rDyȟsFT_Нvׁt݇K\UD@|н_U@__VJw2=<^>WEb,F ]:F:I{!׿`Nmm\:Vtou[_Kj77kY +"QABt;eZAhgapvuDW3>-z:<Yfy/2^>3l 0)M+q%qÔeyxnT@w_r\s([̧kӐ桻lmx 4Vy9SaPH`L/10 aw;%tĻ<b(I&B@)>r؇Wx=B:DBSa_?ϟ\Jl,o1țtE831X Ѿ-!9xa|ysކ/II%@D'эTPa- kPjQ=-!pz#9ͤho,KP Z(؄dP$fkpS+4,3Ka]P6md߲o!IBXRNL;eHRO?Eڭ:%a'i(CaГmd M*i-mKzu̡:('BKNEd[ӐRC_Q ^gp Ua {iVفv?I'z{e _|@?% AfjE"Ya#8VFH%P$AR$W\484pAxG*džd,jlXS`X>$c@ .u%3'ҹhUMۇ]vs(9=P.XZ %h bC?1`'}ap%XfhfX B0On;b@3ZF?&vW3@An`y Jw`8q?a]ÐE: qO@‹J)SB<ϠO 'pxf0>2@=nQ?9|m‹"">wD c~plsRg \8քn+ z)o3 Ӱ2{dD| к>QY܃ciy㩡NM8tYp=>z ͻ]]]]]]]oȿկ\V(:o%L R/S mj4{fN}N/Ӎ9qS-Ҝfٍup˿d/aP!.n::2Ẕc1* VP 3UJJSF4F)`Q# a5NmHj루-Z#X5ծw(v}DGAmPvwaQUtUdk4j#"@cZͦvB!v`Zm{6aStioU^s iXΚ˦ˍFN)'"0 M2sv*)MG7sb"Msef93aG%8mNœa1Z3̠]'p{ 3hiv$ٱLXMvq^Hv\2'1߇ &HdQL*Q *^r~L$+3YcY%jn*+Cf"{>8oF=sA ?}^_Aaٚ&&֘X|M#0&~!?sD^6GRRI9}%kX[O_*}@jP!vl{=DȡhHh PV Ca̅ͨ<[(nݸlWS} 1[[edfج ebVYNJn%:5egQz#'Izb.Qd&2].2}=kZd֞&{7i1XS>؜wی囙<ʛEEȧࠪO:% '-F o? lZ z;g3(5py.BIkb#l_/-f0s,THr*i\pa \gtvAT+vvodaClC"FOPNЎ{6!8z}X1];ӸXVt9=8=gr0#J^eڤUq^Jšr[Rb pfqv)P LV.+>f Hoe6y\Jŀ m^PmxϚ}Yя#2kk93׮dO񵸈 Eh+ ^6TFԠy># \tWol)6L)M$&9d!hJJ<΍&l>C(Jq 2%5V3_>m7+.'z+q|۪Vq@<8<:+7LЍ4̴ [j9hu%z0m1Ś&V!U{J8eRD̩bTE_m&*b߫wXEpT^#:MIG8Hkμ7\h5MftLB*oX5E>w P` HTBjWVd\{Ti4n %Sem[c?ͮ/:ıkW2zSN_X2rr`LMD}>OH̃ӣ[GOϰ.җYF׈X?fJgovMIfNgcjDұ#Fd$d4IR#뀇䡞UUuyGrдrb葏_=f̷[{lF-X =6| ?~78b_Lb5jnx[Y tcuuuubi&XP@F+(AO &L)@f3PNhX(jq-5U$˫Wz]-L*#[[5Q t,9cr/%7&Y?Xv),#<)ؼ6j[բS@-ђ]Zl:]7'ehi09a[8|+|s&.^gO _4a¥,imUyv=6e!:i͟_HY~d}"1'oZ>\j)Gcg^P" 9DN\/ۢ 4rKC0t~tŅS^F" ЛZ}JVDNz4YΚQ4JƠ}*s ;5sDdeA]jelI\ڬ!%a]\ X;tƏ!֐6([~KmeꠍI.u{3gS^7/L&AM޾OX5a)wo6=[?l1:|_27nܩWV~Hw_Drb$tm'G/Y~zB` TSijشDXe:.8lnҪB2j}_u5Aj"cjJ*NeIbXCEC!(dT%T-`ע`RNLXWEW;V鈎MFi%,URSƳ3v\ܨHA{nN.vPs#٪ctbwiu9 .dFAƓ6LR#KOB)Ќт/v?31b_A':eh֠,{rǒj=k#ߺRݤiCG,z+J[\}<2`ϥ6? ވ:8eĈYHq*f*(_Qqк;^w6.z3hJUL(;Uh5:^V-.('0b$\cvBJ 5?,$R3=tG׹s‡Jz|Bċ-z>@]j[lG_KժEw/}{KV-)DJTD5jTJf1&KKe^I#&JԡY\(HF^Si8x3,nRwFX V#ن#Y#QCwo<4 A(mcC2yE罶U?0u`?>E[m8ܯ #!!("]o5!Dw8b5 i&rV&`߫D #,j~pT[-hz+t[iU2Ncez/xĞ{T{PBJ# I*ESh@PV*@W6^cH6>xId*MS R@qWBc+hJ ?cpKÁ|]~ޤTrT9/F`? mw^:g42qGg]l֒f35O|[~MTaTkK TjZl)PK5zҢ\TUWSk$nh@WXByT H&b_bT*9v*ʱeQE-6.JB4c+IO|ŸKȣ7hT6XFx__@2ߦǑnAIţ3-Z7Z51Y_UXZ"^ӱ-M!j Q,", +3Uaqfvc[ɠIfl6:%:5 ٤׉v:<"*xgvhP9< ")arBio>򲽦 2 TeZ]7 Шl҆7"ITdɂ0xcMQx0М䕒:в2_&k}buݞ/u?N ~Dn v*dh"54]njzN+uOjI%ZDE+P;I(EU|&*RZ4!^=6JJRs̒/4PVz]KYR-RpMDEsSAr0"~.W['U~ FNIJvJ3yB$A#~Hf7oFSgOJ'?KNNI1'Y C "O"#Ȳ=!ٳ=z{Q;hxq͟6ċ G1951ژZ413 ̩yEoky  .`IX:$!ZC"+7oB}_U T\եFyHFp-M$&YBkUkuk}C%+Lc >  S>Do=y?0yXK=,IDlXZ66Z~ǮH5D;2bU ,L<)4Y5;3Y˷KS?ՄSAk2 z=1fKXTta-QhZ&(P^ VN@EJzT6bC)t^UIUASE tWMM +y5DZmG\SzUdYSTSYnm_hғ:z+ JѠ+p -# w4:;,H}u_4Cquao\Lv:Bo GycØ-xOKV뼇@3G1c <[ɠ U,[jё$~T_OSW9[h}ÿdZn gݟT,3' qiOn#8o+,\0"1xޢ]O1š``AaT%kR l,`\|*ZP+Z5BPD*nD[E#j%7\C{:u_TCQFzZNooR}zCH>n}ZgqZ|,o,LtEa*T:Un!- ⾤Z~6@'3!0Ӷ'$_γf"*B~C' ʫ#:0K8?UA"zur>*9Ioeˊn-H_<^?.8L(i}Q"w4yM>}D)ES&fMM SFƥL-3Ŝ@)5eDj4)&Ťr + ɰp  1BD (!/Q#hۙPE_'ͳ#zZd:pL>M7g=>5ⴂ)rTѢ5_&~FN+wU0,|0[Z~>d|eL ~p c%!gv)91$#o>,]#ߤ?1!Ǎꉝ?j[΅ϙy#&;ylHH$hy(HeIlIB}*h{A>- ZZoGxU>JRn&sf6YĶ%7g{;'n^ef)ݾ0r㩳7_8_PU] ɪC}'$6J4dj 2`a:pq7-hnp6/ +VUBSqߕy>; 4SRjjR¦˔%vӱ?q_c r6eeGlacw悸 7c{_{Yp M1]LH0UQQa) D`ɔG{ g705+;4]wb?_=렙}žE=-ZZXw;`I8/Z7[OIe-"İ«r*0납tw-Et)[Q,8,+Ŷ\E< #<MmSqk p#`-(dWBzԎU{ؾ־ӎ#5,˯Yb0"LcOPE43 oƵ߯7=?Ǯ-xهcKg11^^{ p18+pA-9 * *Rʔ:5}:9|Kk|G?hޘs9U+Vg8d;lSwrGD|vX:f[A*!pьAtW|xrM Y,_4rb^_9F`ƑF#0nf\|up3Wӟ~]6|"9z1fvihzݷ6el|_{[b?k%{Fn q\_GvkH xECbzF͘X\LW<53jvO^ns3 O7U4qġ9x}imF}$&&e7Ƈlf9[`4D -UCEvs}~pÒ44ǻ[wpj׶y3c5P]nm;aC#1(FqqL"U<ǰ`w_ֽIf>dB1vs0wcqG &i8 gtBr#&Fq0#gsHCKq^ęB j-&G0xc8a琳,<6{ em4Y-t`~!b|}i=nx[UAܰ R"R.m|.҉@^adĚ2wۗY5m=d5 _( 4`ĊzQk$[{N[{5zS`F@wရ.^{^…ǒ} .7r5:K ~O*3 z`:gс6&x(ycF`:w0 ""_q ޸oBPG9A|ӭ#tФݓGc]lkNF]WK%tCcשΤs|p# ^?d_њza˶4p%,zv=5R{vݸqyטGF&dSsb8i!ߧy9Mp\nL(JVl+Yłe*S^׻û{yY:mL,&OfR9ݨ{07>vQ$^bjlŊ0FB@x(O0\,m}.Q.I=ʕܻkn׍[zgp)Ċ|V/KUB9.5j]띫.t,t1V2o[lS6 &rrJbR,yos4gqf*>$>bl+FaBAߓ+o=:$ME\gZ'r N 2*ɶEaɧneロe΁ߎp>koW魣q+HDz:EP]rMn j䢼9.նiB!Rϥ+55 r\l=zǵ1]ĕ9\:T]~lh:g4nzK1vãgoa,^}W~d4|(~”30 !ܔ[ۖ#Tmʟ㜓 |n]9-ێ7*U'1ryN=S+/`ɫ8# `i3B6SV*NIۿ?ՍӍa0,s\Ίxně'75h.X+1cGj6_z矈5q3K/vUNrfs-\;̢]괋V BIb6 ;B_R_RO$ > RFpZ$FF+n~xӕW]t\J커Mk#o7}O ] ╅MMŏ8x3ua;S.aXֈk55za^ZoY/ *dxҌ*K[I[Y(]{gNqҝeJ~Y# BiH8VY*1 }Mfվ]#> }\w=ìI%R*> VV1Cœ549frM^W@%b_dOG'T xk4MKYbWSeͅV-e|.6MS n lE* .+%s9j4Sz ˷l!^J1'^Y,-)nFyGBW״WMkC{Y3'Vw˩nwUM߻2uG6$cs.,/زxPYk;,+ 9Y֬~ӊL6ݏfx8U,rb"d-Z /{6C0w?ĬG^~*31?v\! hQ9qom=.@3е̋;R~NY+m&`cpr. vy-dxp(s6%, M$ YTk>rh%Ŧh;&\e&rjsY6($ "b0.۬]XUJ3Vz?f '%",6q~[T^KA~|yN E[[?aV7GPaFoSA`r,lbتqᰍFSqp+īi\;'oBBAtC;TK>hnѽTR^494^x9-g;O8 ;؈{)G=,8ƫ(_ǝx3oz{O~I +L}δ=l#׾@p(`|s=66_UD%N1Wkqe "T9.V,WIY0_Y^c߰~,9f3UEX(*P:TJfQ$eȨ񰊗ʎ04Bjv`rDa Ɂyy3}Apvئ30M/&TzyA3 14p% S<6o^= r˅zr^9^ WQ,ȐHTud=- i>\wk)ã_ΟzqQyF[*][)^ ԗV*$6l B5*9p[RK\j!ϰ[eKܫr⎸33dٯq]s;w8os'=fyV}~;ן#ꗮ@>Y}r:C7 #d\t8dbf>Sx>2Mgvd-Mq?B47W"f 5y,K1Cs*4 j@y}yuJ>~zBK u;Bc#:fnH$$},c,yt}T]TXWoIxN݈]EC^R}[USxMQ */KL// 4<5xꦕ5eŹc1jXr-Vd0ퟄKh=Gg}hh QU'rE{!zHҟÒqwOHpu-6VϨ.)cvmfF~Z?w\up}ZdVD5f;gNCC's?dw2]Nf;dw2g.Kr1.Fdr GV,U_26+F+j$-Yr&ΙFUn:1?Shd0jdٻ}fBIMBM$<_* J>n9Ed+c6Y0vB|eE] !q/[L 05Yl5{SS. ;Q's?dvCCNnn'-[Nu21!bZSla 'lYXx' S1 A۔FUj\$V.k?ðPVHz-Ɂ x}!\d㩏|Aߏq,bF-3 肥滯nVb?R\%oE'<(0)[B .`0` 8[`h|ttx>GC`j${(CvB!lޒ>EqEW 7ӳ2SFGbJ3Ro&.F5(=w,%|3fYtsYyLk͜oKaBa3IR(\GaѠ4aF:H<:tX4aF:HuN#i:tXZXSXJѱ_NaAa'O?]YsH=JѲ[)K̂i{(<Sx X꿐Ֆ.gƲ ^C(pp,G^pi#f]`r@z W4G?kZIϓpۛo*b@i,(ePA})8hm=,{v51ZC I=BDhKQ%idnZciNzh!EGey\Iqc,nң(UFm{yuwnj Zv&֝60;3_ϗ:c+quG~iB .IP$)FhySFL QZ~h5n<ӱ9vIjS'70FUSchル'2 yM뛨# l0z1<|YmIܠ!y$x6``[aB#V:whQcqJ.ʙ΍=>ls7馽!-eHrx?oR__hcbތ>H`jf픷5)nSҔT'}RŸ4 .GnJ{MҧFKdh|IAZ^C%Epn)f0Fx3EiVC0{̃c6͐Jσe4}.|\[DS*c976X\A Y8?$)wtܙd4ѧCqON辘!DCMk]O ݜ2tG%6{ nW-2`u㳞fo`&[Z0fHvl\vTO n7+#cZ&Exyz1j& Q)]緜ѡD[FEV l ]ڮߓχΣḚf%G%"GdފSy4 ^ҡvQLdzfdU1;ŷ6]7?Ws@M2zx"gzb9>_mhnT1塿5 X@~>26b%F+=_Ak/A5W 6OL}L [9StԱ5aGiqb ,¹vŹ=rfL dSgTip78"s@ S/סj8H|r;M U sCsp/Y0nh[ҧeTCsbJd FۄZ#t*hzd`\-xCWA;tɰe6E+YS7I6sԚSEqBH:mZM 봺V3m>Ζ6'$NAM@BJ BG%Be#3twWUw'N]!z$7 ƺ 5*47C b+LSj dPcDhic`ϱ4{}25 2ui+vNm>.-s¹rGf#x7~E#0Ij:7endstream endobj 27 0 obj << /Filter /FlateDecode /Length 328 >> stream x]Kn0t D I$} Z*2dkD*X?/qgQ0^`oNU W¯:>]cT;nag5^)ߜ͕ms{03Kd z饳,ưm_<&9u#G$\!*8E]z9T^$")vD)H )H JC ~5\j=Z0ERS=<#LhIO)E' GF-UBBc2#;NsP\8:_}9 ^\HX6p/v!*)endstream endobj 28 0 obj << /Type /ObjStm /Length 892 /Filter /FlateDecode /N 15 /First 112 >> stream xՖ[o6)c PH5dATْ!+Ktk`B+;8)D $! )ER[R>2$%! AIx@"8AN$$i# sN޼mϪ,im)o#CObKB@v7یMr{+7T6\Q1//񺁃⢭=Wq;4\"'?fQK*&XwUxçwsܚ_ IO^vvr5}vq=o( .bܝݝR!.m>ŢOfא}nruSnHGQj5m>! ('<;[Jrտc0f4Pq<^LڹA|'>3(C2ׇ9 ^9H5L($YkY&''%=er.L/@xW`FT8 vy΋ L\s5$THqt굣 CSZё}]wt&`Ahn\nEz//Z͌Atf') <NZT #:f=U5*p4"YV5wCWʾC/wwP;T!$P9!rf6*# tm<ɉ5h^C4s4%hע! hhb{ՄwE}%(g7猇{H 6fD0H/2H`OĘCb` Nx8^,loq[1Y;yGѱH?09,endstream endobj 1 0 obj << /Annots [ << /A << /S /URI /Type /Action /URI (https://www.libsdl.org/projects/SDL_ttf/) >> /Border [ 0 0 0 ] /F 4 /Rect [ 175.5 646.5 363.75 658.5 ] /Subtype /Link /Type /Annot >> << /A << /S /URI /Type /Action /URI (https://www.libsdl.org/projects/SDL_mixer/) >> /Border [ 0 0 0 ] /F 4 /Rect [ 190.5 631.5 396 643.5 ] /Subtype /Link /Type /Annot >> << /A << /S /URI /Type /Action /URI (https://www.libsdl.org/projects/SDL_mixer/) >> /Border [ 0 0 0 ] /F 4 /Rect [ 193.5 616.5 399.75 628.5 ] /Subtype /Link /Type /Annot >> << /A << /S /URI /Type /Action /URI (https://www.libsdl.org/download-2.0.php) >> /Border [ 0 0 0 ] /F 4 /Rect [ 157.5 601.5 351.75 613.5 ] /Subtype /Link /Type /Annot >> << /A << /S /URI /Type /Action /URI (https://github.com/clintbellanger/flare-engine/issues/1583) >> /Border [ 0 0 0 ] /F 4 /Rect [ 72 261 349.5 273 ] /Subtype /Link /Type /Annot >> << /A << /S /URI /Type /Action /URI (http://www.flarerpg.org/) >> /Border [ 0 0 0 ] /F 4 /Rect [ 415.5 231 498.75 243 ] /Subtype /Link /Type /Annot >> << /A << /S /URI /Type /Action /URI (https://github.com/andreyvit/create-dmg/) >> /Border [ 0 0 0 ] /F 4 /Rect [ 319.5 141 515.25 153 ] /Subtype /Link /Type /Annot >> ] /Contents 3 0 R /MediaBox [ 0 0 612 792 ] /Parent 30 0 R /Resources << /ExtGState << /G0 31 0 R >> /Font << /F0 32 0 R /F1 38 0 R >> /ProcSets [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << /X0 2 0 R >> >> /Type /Page >> endobj 2 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /ColorTransform 0 /Filter /DCTDecode /Height 155 /Subtype /Image /Type /XObject /Width 578 /Length 20662 >> stream JFIFC   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((B"R !1AQ"STa2Rq#$3Brs4U%&bdt5V-A!1QaqB"2 ?5kT5kK3QI3QI3QI3QI3QI3QI3QI3QIqLYf:S $c$Gx>5R[m/8>>gkkL&a%u-%.E X*c>i6[ڐ'9~+͍?hSIIJJAH~/؍\?_'JCjyw miO&Jd$腕oݦz.vsBܦPR H wY~[m.&"[+ )00; glb*5ŋz#믰[愥\'un:'ᾯvxX図[t|РS->)Q UqL͂ŢG[kCFz %$2CJap68%Vo i[rƢdy&[m[wZ~N X #fixof^ă!r]y2E+*>*!\A_q RdT{|yv[Y EKN<Ơ-H S[08mnކ9aRRd-iPIS8sL'?Qڒ9}eoT{ڧD2Le֧y(oJ7#,;i(>6եHB!JHPX PCQDZNՁJ)Ru`}+JkԽ>rdZ *l.6-!. jKdz+Nn>Ӝ7`ωrph,-D,HXp$^ f4f$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$Fh$hHq@Cj#T Ij(hw?J(Wbs9HwB \ +KpOxn(]0)Ө)WTGE/#g\έ8Am+þZKy`E~:vog-:ʓ~{>=|,^6KxyimN&;T/ZW%>?U44~iw?J*G92y q(c@PR %jHIݣ['j3s~zKW@+l"3}!!)YP)C]9QCBY g+t  gK,KWF}KtҤgγvLnd$gIǎ6eqU{RTδ)*CEҒZZ2AIWPM^a] ')֒wĂ=څUNBQʍ._[ qZqKj.e^ZOwL"g3?oΏK;eiw?J(UWH8He-2E'ppM5YlQ Ҡ@+QЕ#&^0f? j mD}yh G>ZZʇTOQv24-ڔs\;Lf|En>Tdhdz)Y=G~>Ym>}Ctҍ._>()eyϻc(iHRI*[y{i'*͐S翙~}Ctҍ._>)mz:0-!y!DeA8񥖧P+AĂT-SxRڔ'T,P= zK΄8uЂ*]Hj4@3@KWF}Ctҝ?! V' 9 N>Բ~⊑hq?Λ/[K)*zN@z(`Hsj? 'FU&FU&FU&+zWZ]ҿJA3ڈG\ |,NKh 6F3h$QP+Q- ŋV#ZdPZʔ[bY}Ctҹ }uh5Iu O8m$y#ADJBO]iw?J*后S翙~kt~iw?J)FK,KWF}Ctҟkkt~iw?J)-2ʤJ|HNp2I'Gy1.r: iMmx܌$( GL84t~iw?J)ۯ҄*+ %'Z9OZ}Ctҍ._>()kd:]ҿJ4~XcKDpr;]ҿJ4~XXCP+KWO5Yd:]ҿJ4~XXCP+KWO5Yds!(RdW5iuVORjI 4^FPG@umb N N )& v6"o QГ>ZGIj>YGIj_}R?UṒCSn jZV"$zGJO׾$Uڠ:kqVuW_?q_~ךWnrW c&BدkWOTbSN$%+{P} D8̕#Hh)L@$d%_e$K7Nѧϡ:uڞd<|d<ڤvSlC4vamjRT Nԗ-cii Z!#'J ?P(I?\wJRA#?9T x\-qv["R[[AH[|@,}0G㊥.p[dR$>[)ʶgV{$$栱zW3UR֕G^̚e4A鑒zw 0RFsUV8.ChZ.N jY8!oFkW;K]]V;:;Wz:[)s"N`5>d ZKn'>#QJU|hƝ9Lm 29B$V\]s>N``b$ܝ_l}T9z'6Q= l8,[Bs〪{$栱zW3UUkJWMW5RiM#$'QPx%9AU!Y\#G~>!gvCURq%M,BpAu ҿ}k{$cNmgu(' !P[IgYx.N#I0udϓ91R["yX8wӷmd<|d<]͕_{SJ}RSNx5Q iN6# #UZ-UF:9 sHR:OUOQ'\kQRR% 5D7m_7VuR&nQB[!1ݨF/QpB7iBJ9*#RQlj9qneӑ^)wR*-zl41hwJt8eLT~YGIj>YGIjyӛZBZIZN:q'.e'y)W3!4wi*paК5Q5QU<њ _暭r ڒ$Ze*W@TdO\0Px%9A{9d2d)ZY$BH<7G-%ya(Rn;JF:s䖚KC!I#v+,#Xp`GB"OQ'n"Lg&*KpT+vͫOU{}OU'Tӣ $'9?ikeW3TU5Q iN6# #V~YGIj:IYGIj>YGIj:IYGIj>YGIj Ot2UZ@$gnlϝ(ρ6|b[KR)iYA@RI)Y9=A M\e'y7A y!/=*lX33SVαe*iX%D=OQ=ORYuҌx)Li*:ԥ㯍 qZ1쪖Ҵ4:HQ $:m0F ԗFC45z1a# r'`%B̾/7yk~SUa V PtҴu#X4v Q.~+Z(3݂Bv V `%B=.~+G`%hhv Q.~+Z(3 KP^J;Cy)wIA?U䣰\T?W13`E oϑT= Wc:p@# LU!j\gv:МAփ9[\T?WsP!^Jmb\EHD* }QbbRѬdÒȌɈ$d|d}Bv V `%B=.~+G`%hhv P 5 G@\T?WsP!^J' 7IZi5t:ӏ_of+o1[D\ymi*)J0'9N@M.~+G`%z1{SK6ҵ%j7Aj Zz\qS!Pw+ ArBCy(?U sP!^J;Cy+CE{\T?WsP!^JA?U䣰\T?W4PgCy( ҰO=87!Fie-mIm=SNmaM!B[5I"i?.rZ1.^jl|:vwW+Q>^jh;;ëGguy=[5kcl|:vwW+Q>^jh;;ëGguy(ȳsFCҒɎ8J[''aq6;T|kUP-Ed ԭ p $}pl*guy[5 |(|Emfrsy=3uh֒#8LJc~棳l|:"kV:W ].!n܉QtGקRR# [xT0(n-\[t3(]*9NNuu=+u>^j;;ëLcgR_V]}1?~:TN]+ @a)8#$M_fʐ2֮Z7)['=ヾ+c>^j;;ëCR&ٮƜ%OD.}Ĭ@ZNRP+8o!?z,-֛odZHHRU!:'o{;ëGguyhNԃ7HڿnNƖޜlk{Ґim͊B"$&8- Z xuo[5kc."bš4-,\78I.ƝN(G# dZl|:vwW6l|:vwW+Q>^jh;;ëGguy=[5kcl|:vwW*1 )wgWP([lvQo9J1QD_IiZr|Gn[,[.-mE[HoV(؍g>^j;;ëB78JZg[{UT 9SnւRTZI9&6h 1 4 #a >^j;;ëK^vwWP{Ey>^j;;ëAguy[5v?w>^jgJ`M=4XLAļ9N#Ij7!VW-I* 8ΧӺ4^@F3Pwguy[5#N5|g\~Q96/0BT\Iԝ@).lAg̩ik QQ98;niguy[5 l|:vwW+Q>^jh;;ëGguy=[5kcl|:PY_}c RIywQ_D{ZЯ~ʆ[.;ғHRFPe8o\9y\ࢎ?+O 8ӮҴ:_f֫mqKKs#CmHNul05muAV$wxHwveڙ:IRIYRwR K!D.'C"%ݽi;Z8؍H̺p)YqHʐV! N~9|\El̉Ը 8NF=ڝn'츿5QkBzW|onukl7ɍ8UXVJ9‹vo[m.XFBv[j#bFUXq@݄seY A޽ ɦm0tkbҹ/\GʒujIw GZE㻴"& %rCQPTB(IӍDuXeGEDEG!mLs[XRu)@mkg ö#Q_udE⢭[t1L}Q;_{0-DnZ"OG 5Rwlv &q>b KnLA+ ^3ݏJ0QZ#v#p66TQj?4zE.pM_-q@b=A3*mhRX4nBqޱt7ſoɶDDg$S9 |-Te ²3N^c%pAm]Okʐe9!DS[xI롕uL@SoGKdd+JR  Yo쫶J6,w8b,*ԕF7:fW?S^vkMĒIircFig \Lsk Ve3Je +)􊏢w5Ï6f.tI椠 71}K p xMT0u!PV2!H V@ώSoB>]WuHa"*З%QJԜ6;֏ﲭU\.B өJ^޴9'oU.\0f̸)q ARrTq54LY̼JmC AO/dH7Z$pNu.K%ZrI XR1#}3oDIͱӢֻg[AuOJ!nrr v /R@@=h.IA`r"P*˩o5:tp+jISP`0|:B#JD S'3lj!H>6xEdHK .$t 71s-)KPJyԢFF9Ai8yn6A]Iԥ`ª);qDyҝH tQp=͘)r}ElMݗBt$#9=m;Kmɒʥq҂R~q3u&)Lj2嶵g*geI_J8JpPSvw0eGg-/!0SA)Ԑp3F8J*ވꋨh&bTHi}[-=2-ױ'8$绾baH04Št :''D xzӒqҀZJ"I0A؞Ꭷ)r&H#9J*q流K2v:J[ h3I;lQHYq6!m%aԜsD`Uvz}q1PBu`)H>E^bT p9#N̫K2-ж[ #~DQjM{7.ܕ3y2mJa<9Y8ެlRNA!#8H ONB]hW?eOAg7Ź9 !Ԅ4QZNN6)|~$u /mfqT9kSn߮sƝ,]z2|WVa]$GTwW#(*62 +))AE@Ց7 ۍg!/RBHVl8^\؋1@Psl3]pIT:t'[ H{\U+\eM ߊ$QHig9_n1w-iIOGLsߝbkvfK0_ВFUa?^)V\SQ{.nR8n8i$ڑ2;n,$(nOqM'qqmq1 HH:=ߍA#fJPYl6wT\VP/7֠ɆY[* sA =Nt4ٮKkB9Ry %.;dl8# UC+ 'B*++ 'B(9Я~($IW \ .H؆OP\0$-+ o|f1'Qu 4xlO,$m+O.'%w9&It)-hN0tȯm TB fw?WKmxAZRI *I47yv fL$(qG8Br崘VPGb2݌gF sgp>KN %@5'Vׁ#㧻S[[l&} eaYHcMś(5t }RrZ:0ZB ;r-AkIlѭ2-4|ҁq%tdjj.cȱZK @Fb]x׻ .u#+KaE%YRIړ۸so̔R1 ڔI% oղmO9NMJT )N(#@ LM71R1e_V;kzo~Vij] ېb1-2򀥂D3VquUV'NJޞZ p RGTH3y)G־Pn*H*K`%j(Q#FGQRظ mu~y'22ְ]l\ |9Q"Nk|ElXJe1BK+m*8ϢTg|ݸm!/YlͼJ\/8 @Zх )@@߫W<-*E#lC* HQC)P2j?]$dzʴ)D1.2HÎ  2ICEX\n/0%=4**PcI#+E۸9}4*Vz w W o6wgǝm2^i G`WV7Xx]ޖ~Iq$*:֠2sM?ׯWsp]e ۜm.&<ڂR:wUx[焩2&9áiJBqk(QN4$$oUBVKB"d>HvL5$(?xv:wl~Rxz *e8dE SWCu~BXRWp1Me򙄅uJu*?93D1mZGu%HU:W[J+Ƃ[KIg 8#$)z.:Buu,g~6t4RnmdH+SJLƑOAJܿU<ضĭCSB3+IʺkD+%&vUoH8Hiǖ6wj">1Y}3(zh[DPԥ`%98띎URe95dGKmgZrRu@Jށvw5  yi-Adtay4ɇQdIEi=6޸)6뜧mβa KN%J p:Tqls8*1ÔMTR`` Ktʔ4#ˈJҤ) $m \V5jNFqv:d:Y 4aDx vg<! DX%itHA֨C}.c<%թaaA 6F`6|RY.l͎Ժ72}Ŧ8cDk*IҐ7*bn 5Ķ9ӧC#e}rOuWC"bA NsL\q -qcg$< gAC{jZ6@lj7d9;1[:Bò֥`m$zGnG{)no&[ʂ 9$zsz#vI2N'g)Nq=\/e([TzD+H|.S.KaH Lvr.VIO\Ḝ2EKuJR ih;}!ߑ5HV)չf1pV^!Ʌ&dImũ7ʓ 'OZPQEEPQEEPVJ\8kup`%q=sh$_/4tg:r3յ Uk62Kr *6uw]; 묶CIo9+o:|N23֯IڜF c'9|%-&se;rjI7 TWZm+>p-$v ڜ\V*CYN)jwIiScI/(Z{Fۂ<?t1 O!LXDNr9à$(KpNn}Jn:_Q\Q9Ri;=W+qm-6P19 y>ջ퉂 bCIJ)9i-R"*+뎇 !;r6 OD[y [O)Ц:[SPPl\lsV&]j:K졒Vuq{!Nz-'RZ*8;cj{: ȅ%QSJR $ Ii-ehyY %:wĶm%E-HJC++ԟ 1ߑC&!%+k#NI }TخUNHyВ %H8z e(h1 :y%(R,Q  WNq-0@8B(u+N{EcqgYa*);&W0î$RA'QZz*ie0#C^G,)J-%47B6QEEQEEPT -Ĥ{_+l?(;~.5B ^{>9mK:ډ N $XjIu'|Wx.]=Ȇev4vOղW׭,ڔ%82jMĖ7&mn#{ξ $-[~Rգvڱhsu|B^8QۤH'5.ju$`,׷˝/d$_d|D ^-n0]3Fz;}βScAR3#5M!v]%2}Ԍgtg=m-vN3ƾ!ĘRIpP8]\{\hLZ˷Bf=ԅ8 ;ըlVomU(NV2p7'qf+&KL%o-j Ki$Yf- G:8kB(pJ@WP `xjq޶)v9'P = "WCfT7gۈ i=>V6۔mHRG8yB= FoT"7x9m1 h2_뒈 (缯8X..U t+M`m^qNH rƑTit ѡιÍ.I <Nq褜ڬL 2S1RSJRHwȸup6BTNJK!-%ԐIhZoV5JJZ:>TW>m|Ю8-ڜVPqg :O8 Ldw.719L>姒RS u@{ǩ<[ۖڜo!+[eH Ix_|S]!8Ԟty aԙiZpsݿQA<^ܮ\8u/:Vn4a)FoI}b?osW 9_|s>$(GkKp-ʐV<jYҜm8C1CjG5K(;~l<.+B55+xWXխ[x*icKN)'05Jm%$(9_|Sݛpbr/Iƴ4JFu7){ \-Ԃt )$c *t//9 -P)XUB.?po mБVwU-NBwo';%uuyyxrRSk*Y%:Uj@aҙ{&j#7̒lN($d5Y y(m>g汼\.TZc%H8p*X*Ր=ܨӻk &d2>d #E=rb i\lGB|2kL:ܦ(i< >;IAdmzRPrOOu fC o% =&Qr9u5i3}I_+Ϊw&꠴lGy(PƖ~I_ղ|/Žj\QA5K𣚿WPw_(~W 9_qE3pҌYeCvјgq)gex扝E䷭eC(dc=IߡO>P,*MK3@铝ѱS.kxo^aq(>,uj uԠjs9KCAiPs*o+,օ|P˄IG1_X}joYpf&9 m:6VFoK;~?72;\ JtМuuː3!੥` NXw8RZ%dKQNjG5KʹjfFw݈ZZtzxۍS?DS.HZyINJ7$NVZ/K}'Q_+*_/¸jEqEa-OW\xT t:)!GݍW?+RZRV^b" uj:$@+G[%B- r8Ea8?8xV\-SjӬ j:RD ӑ׼x_o%v39/u$'Whp$;'z; /Ixnz;&;4TPTkb6]ժ\xa\im%*IA؊S`ȟ\_V-z(q:GeͶ&cyf4VaZc%D(@ K1744_S:*m8$g84+R%#q-ln[9JKݝ%^Ӹդ/NvlNKo>n%-g- }5(JZQW_>;OpcZZE9e (EY89 ݭCvfhmkRv Ii8 ?9O 5f*jH ӑҽ+R(=iMt$.7/Pp-JRp$ oX[ÑUhY);c6GVr=,I#$N{ox9 =Jk5I+bI2㏤-hRpT1F1Z >x.# sL(X^TH K˔Ju,ԭxE~BExE~BExY2LFɓ`@g qWzUq4varZ#H8[$No\1z$H`yu+HP=GսG!^"ֱf EDqQAHqW A.A-28 #8zbjѷoCr^!j9 jnBEwJB_(Iq#V7mgo\^#j\)KPJyԢFF9Af1g>|kBEf"\]Wla( Y#9=aE~TL\gKeqU P#04^Msߖɗސqi ru$s*1UVFT]CA3  }):x9 C/i)ԕ:tQcv[uy7T@#sД!!)w DHihSOwO}C#c%, dBR.}c+(dqNVG!^"QA_G!^"QA_EX6{ARU5l?(.RЧb>>rФE߳;U6f1G!':o *)q@)N;k(_8!݈Q{$h H)9p 9&[2$[i 4#)׃߶դQ3KpwkrKiuZy m(P[x16#x}s'HYaM%a 48fZ_.#2q؋p';H#qۡ5B$on8Ym: [ߧj(3֞my5r8ff: s+  z)+*6fK-ޛbk pm.{hB58J R@ ()ȭ5 \"L\nReuSj r:TO͋ Y!3}t!ĨO.a#c>:1s8NXċCmKFDti< Ԅ aIڢu- NaPmRNI5.TERJ Zd&J@ )Rp}QX [ZhSc mTPD;}J[vKR6[z0=UdpH/.\wˎ,'? N8 K"52aeʀBv 7kihSSf Z6ˁ)x #`F@P]QAvE*endstream endobj 3 0 obj << /Filter /FlateDecode /Length 3673 >> stream xێ)ttA]_'X wl8%qFݢ>M^!ه;S`_ip=i%Zw?ݿ.vv~ǟX.x+]4+es\X''6!SG iOΆxkMʧnw&!tz"nλlݩMҍDMP [<~'U}͆`C>Tm4púΙn9z Ӆܻ0\D38UW`UI/47>|66'z8d_mG0W[zC`DMTC諯.O5@1U^{z8߶)\/<gcavtr{揶FK6+^n40QjVSo7E:␻tiYޑϑ jWRX# F(:L}z!fX#ӣ9ոit U~ ` kZfDMX% J|Rdz NuUA< VZ-ۯΒ 0EJ"rϡs\ ".RMN- b$L{ᡕHgg !mt!Y=U 1ntt^. YuY #]Jd>QOg0>p% Q?aXu+WuW%ꪎ(T2$_C0Frvd eƹ^R$ɾe[0c) Gn . i"yM VKrd[VbcdC_x(7r_Wͦ`j"7O4\ ׀j)#[DHm %g_M 0M@; )M?jHFb/@^YaK's|}LMTRhXyI wkncFdDd aKO+]gC p#5nK|;BYշ$1Kc+ r(KI+>W*9e0JeBp˒pNZt,mV""GS IMgIK ]BX#N49tQlfhT3}:3њ8C-ҢгA1πUCZOt]Lbdј>KWT]CA"1MPDr瓥yT 0#~׳9[ ^+fHk Q*]6]y&[L^J|'%6+h{5=N_/~ůУ'g%&Qk'K]I\3%7*AS[  RxU['aYw (.+o \nB:t1DbBTpEBM"nN bhE;d7{Lq 't/=bKB6e#7MZхɄR{ 9&a2MYNϫuG9 Jܰ"ڙ볏V(R7UG ])Y$bɱS:a"*Q cfn@+AQPsrZN[B p!"&U X+tV VKKsGR9+4ƛ,e5LhMDI&#=lMF-DoXM.""Z<*sa}K'`NQ^5%&y[Q$ rw9U&zICnAdLcҷ:\ 83r < \8(M=A_\|m| ~EU_MJZ3k !i]|oۙ]#5vgBdaBFW]#YWy]䅙Rz>*I]KY[;?ҋaB<1H9MX(#](a<#9Hd;mkRڨUSKLdoyLSL`9sbC;˨rfœCVȡ_OMN\ 苃G L oN4ߵ*^2Gm-("cH؄{?H߉z/7_he +˺>\;? k:]Z_ o"mvy+Kvg[{Ϝݹvm7 endstream endobj 4 0 obj << /Annots [ << /A << /S /URI /Type /Action /URI (https://iconverticons.com/online/) >> /Border [ 0 0 0 ] /F 4 /Rect [ 75.75 676.5 231.75 688.5 ] /Subtype /Link /Type /Annot >> ] /Contents 5 0 R /MediaBox [ 0 0 612 792 ] /Parent 30 0 R /Resources << /ExtGState << /G0 31 0 R >> /Font << /F0 32 0 R /F1 38 0 R /F2 41 0 R >> /ProcSets [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Type /Page >> endobj 5 0 obj << /Filter /FlateDecode /Length 3064 >> stream xێ8)|=x$`X,JҹA;If;?*wڊ&m40XmH_LvMwD|/u{6$u[~TO$+kB<$sz{3Pl*Thgb=`*O3њO?aq}||y|צ]{6xlH ֚6=7<\Oh}7xlhk$ۄ co^z1hp-HEipslM pۚ$֘&q<=fd- @ 3hh9DW/Ik̂ uFu'tl_~sѨImb[ιN;[dq4;h)ͪ=FۀVGZjl޻*:)g^J07lc 0[\ؙ)wN"U$ 97~A+J#h=E^ 9tH}s6!gj'-;Hfؗ8ud%C8um9ZM#(e2;/[*jiL$k%;_1ɠ>ҏк.ԾE78Pcp&S a- b> ű1f3uiHĪbM^ K"Jz -ZݟGg M|V|'a(& SIoK;va1rѢi1Cm8A5K]|M;֖~2fа}),s~jܜ8êhN$&L`[lҖU:2rxS4/^ʞ6^dOI_~„iàR4DhsRC.AP,$$;-Iyf@o,*C]tmrO> #D}L6'ghŻҭO3,hZܜ\N'QC4n' B=DEbIL6'Q "gX$Wv*(vD6DfAQ&o(0J󙧄U?pTMW\^RR+lm³k;#ڒ>4crn E}!bGxS3̀ca/bCvf XV'ߣD't҇a}}ӆڟ(l0D9QZ^1$| L/g٦N-RkSwy~ϥm 3p)dyc1#TEpke_ЗPPaE}. ^{K 9> uRgUHS<|vkf2r[.zSŠBn`jt=L`2&@+#߰ߛwN'R~A=.~w%2,o2)MYoz~~wi1ۙ =iXij%ogJ\R߰rIys'[vDv~ 3%o>2AկAn[ӤĶsU?>Uٿ}Yc}ƤV>~endstream endobj 6 0 obj << /Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 30 0 R /Resources << /ExtGState << /G0 31 0 R >> /Font << /F0 41 0 R /F1 32 0 R >> /ProcSets [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << /X0 7 0 R /X1 8 0 R >> >> /Type /Page >> endobj 7 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /ColorTransform 0 /Filter /DCTDecode /Height 217 /Subtype /Image /Type /XObject /Width 571 /Length 27658 >> stream JFIFC   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((;"P!1AQ"RSTa2bq#B$6r3%4CU&tVcs.!1AaQq"2 ?55U.kjנK3QI3QI3QI3QI3QI3QI3QI3QUK\;c5cH̨C)r9#GkęU lN0I+͏1z^//yWvs[ܾfBTp a Vqc8K]w$:)-#!)F R\m&YT0.4 Q! Э)[a*J09+*=;W/||O͛b"ő5y)Z]qJr)Im%KkiI)H@RB~#9υ ZnMw8)2'8Vq8en6i~3l}tG8~UW*kXuX  Q:w,ήC2FJu!AC#ȩ3Xr.6-40KB2+]"Lњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLњ4fLz5 u`ik-zBUsxOE%K$!+Yt}w?*cf @I*Q?WߙǒZqrB<өGRMCa젭@9ov8Ia)ZV2OOW~g韣=*Ù%rJTKWR'磐pRyd9%h8H#ۅK+Wց@[kP㚴`޲T@*@I]KE5+FKi!m!?Դ UIQ! Z~UYwg AI*Q?U.JTiw?*XHqPQF/PQIJ{6TB@#5nMGay僖Ro쥖@k@qH |E?+%*)e!C _)e+Db▉U˜? pR<;$RrW.tY)?H@ClMKkDl}ks׆v'6JP°3Yjz]_ʼQ) -+A=SΟJA?Z` _Z]=xWD騟$փXC@oq[Ȋu.cq*ZAGZ<µ`BԒۉHJc (5nA+ARR?+jI5@{)?.tF}W򦝸)G$#Hd穴)nĐ$eJ%efYfGV^,vY  PUڨPUڨՒjU~G=zğ( JTiw?*~uB%>b%<.ސj );^LLƧyNl}ղIZc Yf]_ʍ.?+Sbb[>;W%D%k#JJWHP?AvrP.JTiw?*}QR!JTiw?*}QR!JTiw?*m>kPa%[c$$d$MqN"@qmV ガ}Բw?*4ҿ>)jQ!Mgg'@JTXXC@JTX{Z;`k3P'JTiw?*}QR!JTiw?*}QR!JTiw?*}QR!JTitue)_ʟkkFAڲק1s{?@  c/Poi8켫dy[uW(~(4Ոw[)(#;*HI?TBc\_i*r9[֘q:?ڨi'>j]ē?nÐ_֛ A8AY:ʋ$jbcRv,GZ71/YOW_5Ю#j/y׮M)<=XԭzZ2Ye q"BVW(qXߵW`` h%MyN)C}^P4zn+_GHJ[$ hB*̾u]מjrTVFe8݉ʿRwhWy4>J@jlV2JJ䊪DeX[G@eACŲv!!)wzekd!t%!)#BPkVq[_@ZԎΕ(gw?J&Kvp HB]KM՜co. ]aW EQ5 NIO\ kfqk.+yj>'=RO|ܩ5ԬH۱&;CJ"/>D}`duip`# PRRI۹5\YeSF;QIow޺Wڸ>r1hy ?Lݝ >k}R*Fz*oQ樊M1ɂI m:eX7ӷNiքǎ[eUoQ&\ hem }AW?4Ueq N3GƯ2? 5U0q_把B91sJj(c1Z[8*DhPR-J6*jW~?We'>j*RБA;'peWuØђI)3Y貕, Ќ2T5b)NDI~6mIH,nF:U~GIQsW/)#U\[)g$w𪯽TjFh$F4fMTʯH?RԱe*#XAq (ۡ[GIQck3Dejm^#wv"j>GI]W #C\M%OH” 3*C!؏Ɏ=SuXe'>j>GIi. \@Xp4}[IGIQG:IGIQsTze'>j:IGISG栊rH?/wZ帧\[)g$wO+ؑsP_%G?xW_uŌp;|z4i;A!#m{_)+z$]+R%8TKB;jX (?ET2x7nCD{BP jV$[`H69Lr،5N(jJRޝjwA #\?xWG`(~𯒛Z1.oy/t+wApAgC|v +CE{\?xWG`(~𯒴4PgC|v +CE{\?xWG`(~𯒴4PgC|v +CE{\?xWG`(~𯒴4PgC|v +CE{\?xWG`(~𯒯Hڣس9\F'r?㡦t@!DЯsP_%=wkGj C Op!Gdc (?ET8'防 O';.R[天i(:31_LۣR끄@M-(1:׌ ϳ } (?ES - U iA.6+e22`VU_x@aCL nX@OEgjFNC|v +YhD!?%MwKSm!nJF UmSD;[#ԸYi *)^jN΋}¾J;C||hs- <{ԷV4F3ԝ^#lʸ XR(ZR0sH`(~𯒎sP_%{bW˳mkiɐVN4R@PVl`(~𯒎sP_%hhv (?EV `(~𯒎sP_%hhv (?EV `(~𯒎sP_%hh71 ~Q.~+䧮vԔsP_%¾J|P=d#Jvha璥(aaNrR2@~WE} ̩,F(8KPtlt -z<?EQ.~+䯤q}oi3RZSj' -Zpڕ ޟKgC|v +CEA?EQ.~+ sP_%¾JA0ncPsP_%='Bv (?EUk7jhnrS :rVq%) jR ّϾm!1n*YJ2N5N\?xWG`(~𯒡Œ/M% !?$ӝ^9EhdDa%%e923G.~+䣰\?xWZ(3݂¾J;C|=.~+䣰\?xWZ(3݂¾J;C|=.~+/%RPl84!Z\RNHW2n--ڊ 5G)&sNEѩ\{=v&[XaA.$b)o;ǻ棳l{j{;-Uݵo[K:5WviwZ}sIDbK!mh`-%AADRAZvwmwWQ9եˌD+MW;:G-"ebR 4eJQQ8=NҾC +{;ǻl{j;;ǻl{j;;ǻl{j;;ǻl{j;;ǻl{j;;ǻl{j;;ǻl{j;;ǻL ٟbcNJK+W0B$w` :;;ǻ棳l{jv?ʔq=]MfJr)) A9u:)wgWGgu|\5K*ZݎC8@*;U _6t[댻BmR1 ^uy$;[cQ=_59[̃}vN!;*I@R, )kQ;oslܜ*@kS%~S+=_5[cRsDc%֛62c̓˫uׄ[)'c3qx*["{i+cnQ=_5GNgq- 00wsRt,7!X ^_8Fd5Lʜo&!y%, N[ ; _=_5[cR1?}Up\%EN42.t'em^vwmwWGgu|^vwmwWGgu|^vwmwWGgu|^vwmwWGgu|AT)棳l{j c.k8eDqTzOBv2ŹHش)Z'g yG=b&D)ۋbQr'7SjqŤTjNr6ڶqZYgVZ3g'gu|vwmwWMsl{j;;ǻl{j;;ǻl{j;;ǻl{j;;ǻ/i'xWYW R%GJt'&E|m+\C?_U2ۈ씼#AA-MVty*''I:ti6ޛlKKL.rٙT■0Nl✽-nK)(pZZT:ԊW65X+Ę RB%*P##T{kK=dklmEKl)+JeJRt(mqVb-Ҧ1lv K RlDT_w;Gu #وKEJ!J ʲqQK੷f/+^>|ۚ[KLi[Kej*QPcnq?ƙpbx-amX$n;qԹ3QcL":k < N#oVgY[Vt8 ,R" &=af0܎w+i~4>eNsf".0b}rPH;y6V[m#TDԄHPZCi2uOM~c]jUnBHĶ™/8)$eddxU rzv JfT$RyRBU)AC)r=kY&.g1iCmRP2@ZvPl]\{% (am_:"(((()#חƭn6p*Ov PʝK͇'%8Ja[i-)imJ 4-J䗮JoTEZ۸c**F7VHQ%N Yi)ZT~#Sr[YyPjd:)( =lf%"TR5JPOL9;U4XݱP0S!N J$(v欢\>CD-msRTU~uޗ~\Wպgii[[%!A.+_[v8Vp˶%`W^t;=˝4ǒs:N bٷ Hqʐ8 $ S;%!yekukӤ)EGd/EVmXgٓ&ܘ0%8μ(=qTxz,9iC.(㙀O;Dž[hDSurBFQ$xwR,"ȕsieJ Hϕu(mu sⲾ#J@L8r$G'N ԟ+qwfW&bī!ǂ(<Nzt؝a 4}N16RS>V +6%[,̹otxp_qW\SQuJ,1"+" ҃ PN*Hո;) 8^@=3zg\Uܷ-J -Ӥ,$j>V5m\[i|)(@SE$`v?*((( u&rN:NA'ʽ GJ:d8%!Z@JSi&.SJP\?Xg҂k\ieѓ(_W˭S.%t)jQRIZIx Va5\!V-L޴np78"aRYw|>m+."qts9zzVr'JZT$"JTr֐XTpcȮ܀qI s|vߥAg%?KXIi$Rq pjYٙ4Z۬"[ RAQՄ,[:ʍ G\O=PLǕldBIK+ӡ&;-K ', Ӯ@\K+B1,%œᔗuc랕|1s\#tYPBRTSvNMAo}И.E%@}3Ej8WIBr)hl=59ќg=:պqilxQ]}l2P$%N* gmˏ*o)a-c'X9 #szVqYr 4= DJ8Pډ$>>-͝2-?>ZT0ONci)Z!i(PX꒕ATWҙ%M( #a3?İiW"ۡ%#/7)Sscv"̒ܶU"JN:g¥a\)%lmƔё_L4!-H|,o6p^xi{A+-.L.S J ,I93V Ɋ]BaKC4}`sYMt<&JS0Srv@xt'Y?TKcZ #ho 7li) )!#b 3N4'ʽП4~ThO?*4'ʽ GFhB|Q>h(&lUñ],甭2Quc9JĦPإ8sc"851D5})a8 8rrzQVy7q԰PO/7lNq Fs$(ˌqI8oU2bψܘC82ֲ7Jdk<"F+Sji888Se,,!!nh8ܢOt'ʽN!i`0 Bt5~ &6˩ԒSfe(&JS0Srv@xt'Y?TY)f [^/ovW]XwQ:TچA;t]m^BZZKJJ) ~-8: {?d[6[#.H1u xgjql9ujSe aaӌcouq"IJEq$ J>Mt'bœ= 2܃!?Հ*;Voa"/TꊆbRT@QƐqn0iSt@ Ay R;I ?K>h-g %D-`2ZPS)*?:qZ34'ʽB|Q>h(<П4~UH._i\k/u? gKe:8y2‹2 8P;,yNa a_qIǂ>ˈ}JyJN ;M4}oX-lz9C, i4%ctqA[pbo^CkTRҰJ'm*3i* \ߚ;'hK “x R3};}~4sƹ8;nGlfD+u-ik)˭#!: /p.ThBynT%qCQrvf^-ֹgG?kp /b:od8u,lr6 r_5Wov#Ql2{,:?h`'L5~e@΂"a+RRBgY8Uۥ%8:\y{% F򐣶:8Ktxv Hvh \\*RRU@{}8Ug@xk(xW~5ƎPO?9gPQA??hAYkbNJ:P+'?hPl9gG?k[snV<ʣ\p6Ң[FIVNp]/6iģI s7 ?hfRI yېSM;wz!SČ7l嗹i 3}\NM##Vʛa,(9gUPiQ;ɀlO3)iR5) #VqwPt?3CyV塗"65ŗڵIVq\o6bae%26{AйgG?k1f8"dҧez# I dҗ/Y1$ӌ5!ucBWQ׼g=[{?9gI-[s 1S $1x4_#n[W1a!Aw$a?9gXC񮮘s%K1YuS\uZSeJr@PN0:"6Ʈ&G1Vki* J```FyBpsƎ^qWWu:鄧4dutƆ@Tm.^KoAөu?9gYn zkn -fGj*Х B<ՊPO?9gPQA??hAE~5ƎPO?9gPQA??hce+ ě-|y,Aӓ6\nU.9 )H?X֑ ?ha.Iq/~pep|U# 흅;~iqnHmKmaʒ$~3D~4sƠ~4sƹp_d]ssq*R2 9 A2 [.}ԸӮR mMt.?ǻp [^~zZg '`4Irju2qRR#zQ,~4sƹ/kb-M3#zCƢzd<(AeL4T0@B#5Zٹ͹۪eL"#+SmJI>)={ gG?j ('(~5Zq3m5kqw?К^iЯ4Un ʩ8Ĥ7Pk(` 4ymߒXH:9wXIYKr\qdVYB-䑌e{w8 Jr EKk QY`RT "-hftޒXBImkTwԘbn-Z(!njKyuպ-J*V;${+#h~Lou^@+ ' O[0EnI"s,Ӎ E CޥW-j99@:)Ԕuc9k"ugxHj0if'):Nkc8U\)h xP2JtI+QVg9Q6ڶ ؘJ2RKee:RN V`Tfn27RЄ>(Αƥt־5&7J W) QKyH aEEe?n\D"$XnS!  䃸 ^Eƕe[ikl9dI†7cUE-4RDx>_zʭASBQ^i4+?ʭASBQ^i4+?Qg8HsӠ%$=$)]uR#1I={PJv:ʐMh F5'R9)ZQ(-AJwP~L}1e씁#l[DNFqM0j^iЯ4Un ʍ OV蠩^iЯ4Un ʍ OT4d:NHJ2(`I«߸#-[j̙Ia;Tt1k^iЯ4T]g?_yx7+8@9QүZ&\:My# o$p.clC9YL@RJu`^rnKfJ%R![ӸTV; 2> !EL?B#ۦ;ﯘ7$^tMd8Jc:m즆iC(LgK%e#FCJaP"4Fyl8HrI'r}F92j^^at wc 39APV$l{ޅyBU(ĄD-(JSG$4G i ZZ K$H|z_HO(y9AxU[ە5ڂu9N1g4"H4:Օ}}3$&!hBVPJ9'ib%crC)h H^0HqP@63V &;JIp1 dNsRW,ݏ)R2){yYAt1Pöc1]Z*+*'Q9$ dl*¸ҘH KKR'u5 wmPHiV Z31*vPLŞ4y .%Z %\ş t+^Я4UQ|AoaX%%*RU*X}79pH9j 335QgBQ^iSBQ^i4+?r.;q\FkW km4_𣚿;AjJ~(QUysW|(-Vvd6dpi!{2՟!)sB㿩 LIX#Nqu{3ᩱ[-۟HhG=lԡ ZA*PVqV~.\Ie2B-KR9'71.:mĤPHڤS{JGzIe.Gl!Ĩ8e)!GA -+#>hG/AjIY ԣ h~^On&T<myOBI# z="ͫ$A-V$$a?ZNjTw-N,)~:PEj8^d>XLt7jXHI鍀Igml!܍%D4a`;M:u1c-A?y+6"%ћuN$%_qi"[T&sRZ҇N$%ʣm!r]xj6O+RyƜJPԕ$SHʖ2qM͑]"ޔDU:HTۈ*6* v<$_<Ǝm rHnNLEaOI}YO[%#4EܶY ʺ-$"e*Swӌ.!+'J/v?Kvg<! DX%itHA֮ =1P^6zrbsER:JXPj K6nl'RIN\~5MqV VӓոwqVa;,jrA),ԕ:9au8 @#b P8@AJa6HگU6esCX JA RE*5~wŽj*5~wŽj*5~wŽj~#FAy u)۫B\DF $o۠ޜW|(P"jtv+PT*o:\#9NԲ,Mp s\YV(V+!##d|2jgRnfnr~Y8qZm_𣚿;Veb 9*|m^W|(-W km5ȸq\zu*^BEx ېTLr!4I´lCJH'gHtÐRu\eAD9wtڹ~\Wo7<_ Ie yե է+C$# ׅEBq˯ӗ5hV=6ڜR!jGd!#J:cr٣R6z vCw/QiL=[Tc0O5IpHI)NH{Ru?jK~@t 9=WjF/}{+QWM-ISK[[{J (+Ps4)zfnԉoqKJR0JR0 ̇K-gXR`R=H!^)Ms"GrVrrVrLw Ƒ˻[8xǑЭ?zgx9  g\!ErbKĖ% S<Ο>8@V85wVG!^"R**^BEx+0drlH7O%%j~qE/8f<*4dVpU#Ie-9-GA1MvJK%Ŝiuk/%}1:jZX# %.Js~&ɜIߧ+)4N[!~j;YbD)puI).(4 w1l|M~y6\`) '~H=sОwJbvE1%QkSg`0צ1Ueq~ПrD{IB#q|)s]+ !ܤ,(~` ݟ7qZV2I7FB}Nա\kӫ}~t} ;h׏hg$Gǹa ynԟLGz-:H##J<:檹1y%EXt%IJP01B4P[d]rJӏ%htN#'tǚR[y:WQib8eXaOeK\PqZw靺ՄXf:.lvPJwiV]>Ox R1yz8ӥ9H◉Xm 8KNm8Ƙʱ Po ISz)#N. J#R )qzNsQbPsLx;2`y ÉqE)Ԭc~5~+\"ZB7aA7뷋v#̈́ )a#IPgTYЅ0˒/nTPQNvoU53- }o% w% lX8=ժȎOHCvBT w.'#N)_|-qA($ye#8Rv纙UӮk?N|?.tE;8PFN7?U݅%PNzd"e*W uFr_gx&7R{d>wN6e+9HbAE똨 Kͺ+S3E5*|82 M2Lwdd򄶴 (ZO1$`\ă<]bޟ"cKaimq(jB v{d᫳3TW#;=_iKyn-t ,$es+$X.-ma :Fp±^fxn%0b)uDU2Ҵ8֝œAs{3!qC.[҃4 SJslOC٦03#7iLf )!@ V*5"8O^[XQBc=-e )B 'n,"ޞ vK FސV59h^S*6yP:e! jdKmIʙ$ B #}_^i'eŜA2.09N >G\|vw`;r \2I3I8k;U_ܺq4IaLmJ-AN)_xP3b׮/%Ap6%'mYU 62P%RGOɛ H(9<S!+ִސxmz3O%҄ѫNTGP٨ 80b 0ruu8j{ge@Cl#ZĆF@RTNi[JD;6XހN Y'a{ ѭim Z}%Pɏ*#b9<2F 9lpw;ATVN% #||k0-Kkb%SMQ;' v1ːeƗR]l,:BIN랴H\XƉ-&(JB@*ʀl˓{z."֕)GQRHH٥v r%-.ĸ@AZF55vϷ5uycIN:1)S*?1-sT5 'jHB fVsJ'J$g4=ȵsqpYZ8o)9kʇo٭="!b +*i8q$5ҨvgNăO%iP jKx~ q֖c; ] s)eY;M͖_ٚė`ę 2JPV):pzU=4(!0eix':I#rgEz"4p*2J !}z[m +RFV굓(AF1%bLe֦yħBԜT tx8K"IIX N3*1[qKM`f˂#G!}ס:UEMv+ɓ!#5-U4-ptiqP $VS2((((I- -̙ @ 3N++>{PRe *$R569nʭPVcrGvZtKLQF{q)F3!_#:))FYn;R6*Sd T,*ܠjֆi֥@hpq߸'6 @L.]{KJmZSR3S dP[ZP.'JA &B|MKE\9 5-rh'ԴPEORA!>&B|MKE\9 5-rh'ԴPEORA!>&B|MKE\9 5-rh'ԴPEORA!>&B|MKE\9 5-rh'ԴPEORA!>&B|MKE\9 5-rh'ԴPEORA!>&B|MKE\9 5-rh'ԴPEORA!>&B|MKE\9 5-rh'ԴPEORA!>&B|MKE\9 5-rh'ԴPEO= t_i\k/endstream endobj 8 0 obj << /BitsPerComponent 8 /ColorSpace /DeviceRGB /ColorTransform 0 /Filter /DCTDecode /Height 177 /Subtype /Image /Type /XObject /Width 580 /Length 20758 >> stream JFIFC   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((D"W !1AQRT"$2abqr#3B6d&45SUcst%'CEf,!A1Qaq2" ?_y^uq]ypUSW}+:U} xq]PjTo>to>u5PMWxѼWx:A6]F]PUwwCTo>to>u5PMWxѼWx:Aԙ]3,s@-#Y*RHb?sMc7uO?~o|1f r$9="^#[WZW)(J-XH'u3"SMԽ\vR;ԅr5tNqUC~3m+ +  MWx׏]nld^JpеH;)N8;m%v{ ZrBT R8r2w:7:̈́\dW󉁼v;~P–K#pO g4m7kzX(rpnRq #SM H΍μH.צi\9NƐ%)x%XXxKؿݮQmtZ۫Jy m qisH\M%*Ɛ@'V1CsV7:G/1=ӦosRa+#C'h؛1i:AHT*uiui$(Np 1QFL+|0C-PN ֠X7GݙjJncnsQZsEsĉ ׉N}>khK)@BğsVp=΍μL7Ww ̾KQ)A!BG$v=!H cq`G]n Y5֕ie<= 1FJ@' Zz]F]Y?fzTX%v@!„@8`~ڨ:7:U&+|+|TjxxuQm΍ΡF :7:U&+|+|TjxxuQm΍ΡF :7:U&+|+|TjxxuQm΍ΡF :7:U&+|+|TjxxuQm΍ΡF :7:U&+|**(ơF n8~UK&W>JPTQ BU@M-Qr(u┥}qHJUZ nr#Q3ϝ}Z:wBoG7~>\ieꯡUƎ,2B _ @qxoRe 4V3\G]Tj_{Et7?/]#]Ru>B39p>TuQʑ~HmԷHI}75T]vCeև]YҔ!ԥW6*vLM"8RHPHI IJeKbv=@U)X*gI (qa#MGXFlˑ3 T A˩K-`i_0%<:*BjYvi3J@cbv?LVMZ~٧=FLK*͖#/!ĪBԡLSG!Z C-HHyR-kӟ5kի]4bsFh[lki`qR riI[7o4ӧ-*ό+m) V:,_<{Xu)\Y+d:OC.Sv[i6}A@Z!,q?A{d!Bow!9'}?i:Er0mK\vR2v?,+X^a6HҦF|}/cUBlfweUG}M(pE34HJVY[Zmޤ)(HO,JtkLD,KR)JR$IɫE1Ps*ڻ - Q%RA*<jEU IyTZUڨPUڨPUڨR[Zmޤ)(HO,J&ˌ5s_J H+RyA5WL`avWKk},@Zp Mz,_M,_ϰ)<~UÏQZW-r6┣p#*E1@UE1GE1@UE1\TOX.迩n9FB #vͳoۑYZm4jPe 6iкn^c{;Jb̨Ŗ@qX~$dwSq^AZ@ <z[8 3;q* mvmnʇmehajmEhÌ%ĥn$/{|*Q*E$?vxGC[f2/m<L}QҍA \pZxߪAGgHXFl8xWOP#~ tkIBU?֏xߪ@]"^/8Jt=!S+VFF8~/$a̟M c1}ߖO}f7vIIDq.BbV)Nzχ]=ߖ=ߖĸŗ.lh~i#B>Vc->guwE}us3\kQE[[f:A=ߖ|go6_He jVANA!G9#KR| i|5;G'j=g?7Q> i+YOMzςo|5;G'j=g?7Q> i+YOMzςo|5;G'j=g?7Q> i+YOMzςo|5;G'j d{92USKe#G1'IMi"Fe2\K8Q-XBrp }G'j}CƒmYrE2n\Y-)R܆֠ Nt"20!?̟Yzςox]l)m2@yy֛uKZppsMRŇRY=ʷp㚘V!)PNXz'j=g?7Tm翳 Y$pDKT19.jޚqelw˙&). Ҥ RCI zg'j=g?7UҞD݅4g}2H7 I)Rs%@6u7_9ejC,4][o[105kqt՜ץzςo|5#iζNYҞz}W]LY/iyKFRCQGenkڗ",}'4Fi -I^R_2HYOMzςomY[򬊍"4y ϸPp(`*|5;G'j=g?7Q> i+YOMzςo|5;G'jA¤֙ 0o_}g?7Pc-)mGiD\yJN!gW+=vY&u{ Y,ЧwYJNa@z GWςo|58bۣ\on \R9)OJUDi9W4!%j*PHG=|5S+YOMzςo;G'j=g?7Q> iZhIA;Zڵ#)QOa"E_~oMMPƿ7(]VC %'JO ~;(u+eI#Hϑqm[DQq?@hPzCoSrBR $@H ۘ,Ęn1'qMƒ[݆zpB4S*rGL6$+/!M ̌1OxF^f*b9p! !hp)IQB ]ui[X4>I3e"Zg2JS-+**'OnxRޟ٫fͩ):e}$(i]jT8VhJh)c#PIԴV:SF^gGۢ΄,Kpx}7hYk>-La|EqChACRBvH#c&@lK.{1Љoj9$0*n@ݮ*g'$n%+[eJ))VFJO0xVxZ c6Jm4n'8$yݵQwn%E?a'KfaIkco8캰!'Z9ʲyγ,wW[]/poI'99?\71۶*{j2#I`k+|jkeBb1ꜤǗ:NKE., i@"3H֝I$rI'Fr}@j$i')/&sYu)qTH:O38 TNx3bA}~Npx7LP[o#,}blյ0v[#P]JDgdr{DEeb}nґ8'Rr'Ǖ^\[2̭ N4@R88$r =un=nT7%ta䴧P P)W8SM&c+.>\eZ%J䓓HOG?#QFs2?EW><5spUQhc5~-{Mw*J<:Oq'uB^2ʢq!߯7_`rX[eE[\ t 7)ɠ22 #00nzlifhR-gx~ٳc 2laP`@eC9(_׹;~/z={Mx{PF`ZmY^ҲlR%:R,7Hл+ekƆR\8d;sGRp Fy'55f?OO'ѸOi#"ojyOXHfVc8\m $ YP!TfvOga~q:xN-H() ^t_,OH G17 5 %EjL[Jݩdh!$jP jmu7ɭ̏TljӍ*&/yĐ[2Ip7X+G}3Q~j4=wpF=B^NٺL':R Լ:@3';jf( 4njZ*4pF=-{M 4njZ("'ѸOihpF=-{M 4njZ("'ѸOik#+v=9e HaMeJ?{MK\.[iDRPKh!$HǤ3Wbܮ}$bi8.BrG6;7(2s][1;~0Îa+X3+Ç*v.S+z9ǀ45ۄ7(2sԉZT%*I)8PƱIޝvR% uI?$ـ;jL[bA ѹHMblcI[ru-U?Q*xmWP, RΗ #=y p5δ^trF=qif4B|}Îx 9xKresdv[LCcHA$$KFOA'A鈏-f)qK #N p(Ⱦ3)֭hgMte?YD`#?sEm="|wavD7_T ,@s{ɰKP~t*PB$(\4؝ݮ'ѸOin%=,jqu(t9ήǗ#AseWݭkoVe┡!DΥA[j/234ސN9?LȊV- TޞI'ZT(%׻ߴ:u3tms׆3pa)e RlCOn9p[ LC&l25(q'ğ«˽"JﻥQ )IFPN~) fi o}t'U+c\SْDAR !*#^G,zHw/))%,*#SiLE˚$ deG'RᎾe҉ M\Զ'*gmk]ԶzE|Rɜr2ܡHmJ+^q)*<:QRk\8S87`(=:Kcd>c\"Y'V=5GRu+an(*J#vy4J֣ B)OF3wr$ޜ(I'6,ُ͞$4J Z #CKCqN,UxHezK$jiH,xSe&BSDq#(Q ܱT6f*SFSDhT9Q q)wKknC*z*u&gQ\Z뛥k%JSeH<@e?hy^Ŏ"..\B*kJpxgS }n9%I/~fP4x@9SyWLInƐC))qEAPj^#jiq%H B݌S$ QC1ky[νcUU.%0 iu$,'HR>鱤J8ˀ4:3Rv}uÖZ53)A:sW2ܑ H)K{HH98]Yzkit l-zI8Ulw& ЖBTPC AivZ((6{3w@5i8h&ߍE~}M-a^}֭''To㷁>ˢ:..KLw < q Vv6atU6C6ո UZ@miݔn}5~5#l'|ysڴ-̩6*L^RԄHC(=`\:mvy[ϕ8$.ʁs^7wNnN^?qً mJ)d$[Jzj].C>5r"ԥ0uѿOJԱ;So}v4Ly֢R W?U5wDN]ǔթUJޥ5RTNrID^ſ7wVf#~CYLD&5JSEMTa:I G6g>n۠qHNIR\Xku.!Hq(aIWGav;§eB1J !T?Ltvo=='vmt>{OƑzRXKe%s'gDn(ͷevSPuiJSS HH~4oƤMDDqI=}QsNE%&huyV㒸lѶKmWyS.$f֡!$=# Vߍog^1L6ٖs emGde)_AX@c~4oƂj*ѿ wwFh&ߍ񠚊~4oƂj*ѿ wwFh&ߍ񠚊~4oƂjl PFypJ)I?xoƍ`5Bm{~[ԄD2A8=նei0J2F!v~V~4oƙ/X흞LoLWRR0p˷98Cb#/-$OَoƍӛŘԧe6֢cY#VBRgZ\9`:6G$:cH@'CJm{Pj+VJaX}kXQŽpK62m:akD4-EI/6 :Gשy K\J!mnRJVԠJGN01DʐqKaƮR[R )pj#R'Vg3#[\?HiL% -dd-KPAlURk٪|qrK*iLOOmJS=P R\+Um[ %N.ZpaD908 c\vqpRѿm2^KR4]l(%@HچVeۋhͶ~5unYVy:** =|U٭lT&cbܖ\ӟ`c_{cmn]^6MuЂ!ER0S9ٹY\e 8m-+BRx)#q)GͰ7 S6I-SBzФH 6msgl̈WeȚS).6$$P-dr(tr9q5A'VDgv>Y-ٙ R#d:#%\IZ[g>Y阶KָmB5EJRID1U]u^~+wmbC3n~ʝRGWȔgW DPs~:Iԯa= R[ dDDEGbL脭d'0Ef9{|o* gLmXV U4WZ>ThWtPsEu]FwO4WZ>ThWtPsEu]FwO4WZ>ThWtPsEu]FwO4WZ>ThWtPsY&.Ei%NަIzu:>UZMNcZkgqx1{/)lCECQlWI**NLnrC,"(gt]niӺ.jУ?Ɲgꎆ BCIa )CJ  9]DBRXR^25)Ŭ tIE39S]Y[S<3\zT/J5L&䭅Rsz4gȚ lrgysf96`/n,YB}!9dr bsu*HjCAH)g3®?8ouh1i#9իu= !+QZR(I&S-Q@0u#QEmk5h(( (( (( (( (( (( (( (( (( (( (( (( (( (( (( (( (( ((> stream xݎ6.@}(Ml1egdڻ ҍpPvmW#wnIn_X?mr_9/_>^7Oxukښ=}f$󰍋զNť{%sazh64)v]}^uӟ )†!SCވ4abJs5$U2a~eh_Vh^ʱGf qkk6v9MIt"Ŋ mc7PlĦB`Gs<#Hv΄sQ(#e;˾kn)=ܖ;2{}[ޥ ¡2vԚ2yKO%qQƲҚ{h⇈Z m?"l# ] ,l<4mn^KlCPϝ*F{z "A~>Ƨa-l]ݼj)`85B PA=tUk,DAfW{f6'z -Q?'1V|sM=آۙSy^pSuh*t1<pRx{О| 1=mK/m?ϔ 7H\<G&Cu%# AT=qx4AkycؖU$NFvJٚ 19R+TbXCb!;by G+2<,Ό'ME-D?\hKroAk9laBS'o!@P0G".!ѳKp,|zON֓蠩>ᾤL4"3ԆyQB#Ҷ>X=VXIzPVj`A>X$4ig-}lNlFIO3)dJQzN†PҀ})gEIQEܑo&!h%#'- exQJRwG6s(2ihgD Q* G.(qvA5$%_ ѼwEW#*-1&Ih1:[UT6N=j;E#lfqk'7L(A+Q /fFmY ʮ7cWՅaOUlՔy =a7aB82)]O*Y.=/:0+\J3nj|&TI )@H6Yc>1 (FoR|Ml3KK_w=\k,9ڬFt@PP3A]QLM>cN7|Q_u'-S'dn&Z.rx D \z齅A umii҂e |"1zJ:uHSlL-[I Jv"zչ/\+(2t}t DO2z4;|㴀XhC%*+Ui?, &M@V܅@^Zii% /)vf@kPȒ6tej ,zFrK$G$pTBm79zНCZ6eH*?+j`>SNjG[2MXnΥb>2;,3-aR'9 |KptuQ4u[rv;ish8GS]7ADEtih9kUy+(P'QGb { %k ߞ9lcT&n8-*Cʱq>,v&Yv{gendstream endobj 10 0 obj << /Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 30 0 R /Resources << /ExtGState << /G0 31 0 R >> /Font << /F0 32 0 R >> /ProcSets [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Type /Page >> endobj 11 0 obj << /Filter /FlateDecode /Length 2292 >> stream xݝkkFWυ(ڛVR8[ m(Cu.Wy=& 4Avw2;3kk?}n_!.{0^Ħn)?^&5 n6 z.BXp\kt\2D+աyx^eM:2v4Φ=5snŰ*v%F|fYR%y2:El>y6 1tKickjhX0$^ӊ![U٭UNys @:Qq6*6%! #-V[ ($U/i4fyK- #&R} MiWwcPxS?g@ b td~boqMP1F-޲1tF4,3B"q+ZTܽ!,_ V>Ye΁Fú6Ҝv)wZ%]4^|VF9?hч[ϭ*VߺeD,epB 2,- ?P&+M $AĶ ,>dw_2l,CJXؐ6<:Bȑ|K*,?f[ Dޥ.1џZ쁥Ճ'L`Ri)ՀBdplR> stream xZ t\y߽3,l ,< )>ByʇdUfֻ&" oXUh4Mўj@9ϧ(/&.ċh냔-ZRjSgy ܟɯdnjtF}s` 3` :σ$|}!}|vEC;ﷀhUQuέT|J:WӐmPfm y'[~&uJJ):O\ok>şs:c!}f݅wgg /wc$T\Fi vNw$j1Kw+g)d}XQ>ֹ;6} 'l}^vs9=pЮ{x_S{v>t^zRڹZk۵#2;ȟy3׳ijSily|Pib8["Q%jE\/.9s3g4򰱗I/f:6M{4333߳s޳ƳitzX8]B( +&@إ݆P$z.Ux쟏}{E Kcpz߈~37 W⫡WyWvl\ Di2um% ^d|:/3Yl^/3g||/ y|1_—e.gW f!fv%al;_l'<|bװku zv"nd&v3ʾv/=/mvvž¾ʾ+Bv7go-6o{{ #({ώl%0;R<<>|^=O">eb}5o먓(@&]NWPzp4  {=o{#o>MM7_vMcu5WU_t+ϯZ3-XX>ws3KM): R^nřFuvan\ WA"[mBܺ} YϪVӦֺڒ6Eʩr.aJ&}뗶Z\5e5[km1B6rom ek7g h{VW !8}hPrf!3iz'_YԁS,Q\fvr -r?oRյ;zVXX;|y׍omVHZoQ%va4lj*kw50[#fMHYu/Q7^k;(uy7~[*JRt 2niџ̪nCڼFȚwʔE%pe乳D++mZ z\WbRWVlpHlgYyY@d9k6th3j8hO&sD,gCzƠFdEMkxۼV̵fMZU.id/Ϭ9X+k$Y"1hc|RZ}j`+([v R.lEĪ*jhUR?mn]4ô:654(-tJ QzUeftNmUހ]v.N/嬬ipYhX,Цдޞ3Vn*kgj=Ze&ɆT#0~).a4nUo޾/_e>2ֶ͚Npo^mVZkܼ֚tI>Md~mw6®X]nSk]Һ_;S.^_}4ֶ_Kt_jf-([`AL~&,?_a'~ ]QMY\?t<1@c {/=*5i> np7w}^G FWq%K]u숯>Sp N)8SnNrizuOrQF.,]Ejs-R6eVٔ~NNm_́h5D'gW88T,7RdSvI0m<}^@Z{Vrœӵ|2-}R5 Oܷ\yowpNy!weizޣEϣy?uI-糖k.ڕzf:dj?E/;&z *Ī`PDͮ@<&F̈D\67]];"hֻPX(pl51(*V,DZ7|@"bM80QkF _<W.6v_GEsv3&1B3O|Q3_b] 2z %d~\ h& +͐! E@a߈]!K+!-tE TUVVt;1vpw,7E/kFM~́pnoXXq^k\'V]n}݆@R{3d2h8bD;DSLX"˰:vᄔ{(N vbF;&"?C2 ]Q6ރvȌB2>_z eQWpxd1]bĈߌţfG"p72OA0bZNPFl"ѣzGP4f' JԘx*bgveEtEP\#euߔǪ\vRq.hv2HQ,!ȇN>]6OWjZU_*mސ$:& !iצ[!!;іe;GC;ĕ #&Ȯ@!'hzܱ@8c1ޑzʇC D*2A-5_l`ha3n|9yrPcr*-l7DL22 L1c&8mOzwR+3B}P6ؐ)ĨD Np`EafoI &b?'+*7:[a99ʰF>EiF HB,kb9Guw3 lQXOnv! ءO!R".g !~a 02>pB}H%OASMAlfC^RZ pNjsq:#ӣd'Cp|gpPש2r0,q5j(:j(;28+H&?NJq4#33=D7̄TlǢ[XVnjЮHט9xSz}v:܇P]WO}iUޝFj TҎdDKH=E2+2*;sqFS"; #g~9]BjFs26FVF]1{^#Nl7~P~jT_YYJun2 ɮRƾ?uԞ*x?or'6DqH볧pj5i}+mgk)C+A;PtƶН7ULy59ߤzǫw) {JW)3W$@_ nG9ӻa2Y9 qZz';j8SiǸAu~sz$xܩ$L>:1g/VEskS&d9IEݙs>%w򩏫S#9D4ӡe$bjZԙ?gfuy#=cgOVPѝߙ~_TƲGwSȒ;S;TBS&PJW!ΛXNj3Ƙ=OKuLjJn1tK\ut̛BTf?Bwah팏I8kUqfݶЇoO?mi~]Imgendstream endobj 13 0 obj << /Filter /FlateDecode /Length 223 >> stream x]Aj0 E>3I!N)dii8(")T`'@q&<95llF=m:rD5TwQ;\+;dO3>7.H *upI/&̂ v>a{DhJ_E64j+gN!A2\ܵz,=sPvc> stream xw|U0~پ-Kʦ7$d)CA@aTT`DB:*D(*(E%M(~? ssg6 w6}Flk,ҧ3܂7H M3OG^0uw,Ϻuc~@v5'Ly8)\-;+c>0%#Mխ8u*W(N6n ydl)S<]1A}xucWo` ߼z7<aE`Fo"׍֖+px<0J#-a*p<`*}e%8v$ߔhppE">׃( /? @ل?%SQ%&,#aIi{6«1 z%rq,x*a Zb#Ta Y0*a,A q-I d7,L(C`l}Lo. xp%!s4gpnfk=G$f2Wq,%Uh!HGk3C-/I* A)>t8M~\[p-ulM@W|cqUqy\KC 18A%b/~8';wg @l!;IGr71%e"ȍdFqN6z>';bxe"<\̭xc0V1`,E0Xda>p6G=5 c9 (X #V2EI,CAn^6Z<ZS!))>u+%$ va%$xysXc6yZxs lD~B~ s 9 D~g4=*_TV~Wq*pP9."Hs<ǎo;~$Jibr7p N(ޫa,RmPXTCoRk = iX7T,=ps!Fs&9 &Nxބ#**2uTAx2 C6z!nEk?4z\M};xÝjOF b7E υ4Hz^ |< EՏn o#o?AL4%7^H݉d&j+G1/a7 5xކHػ~G~Wvx<5ys#7!Eb +YLjHAހ&BEG"ȧjldL9xG?DCR){Aޢ%#OgF2 Ķd,A CT G v,C3bOvl@MyA8@YWtrWy ;ᶼ-lr%B =&G$w=kk[Y?A0q@M[~N';ȽV(0CHEfZف#>QO@ըI |cb9iD5x@tx Ϗwa/jXM~ 9k6H&!:VrB$|M~&/o {E8L&YðX9P?# x =Ͻx&݆) N?^i6 &a%JrG=rFɰQw;)^!ȫ3U|!X<<|7it$ u Մƅ ];wuyf"Wa b3xqǾFAX3"wsmZTzɌX$*p4=MlY0Aߨ5j>AldG1]$˛4|gfԑ_W|uϝKز aۿu';XϮ;abE/ǻ/?^rsw7'~>}s+vձ׏;k|Xcy&_/ldlqj*c2`ee;ddO`9[ʔҨ{Wғ/K_*-%)!.}BZJKIr^D|t RʹpI=qk=\򏽯#=^/ÿ Ym]هo[?wt_tYKWpp 0{ж/1{#H08 +p~A˽moЈ h_MhMơ=D~}-J裿#|{0 T?סG= KDdz<7_}3-)܎KG Gx"&hF_a%D^b<^ԙOQQ4^G֓g;xl@:RGd!YDID@[1@&&bF}z>_yB, |I&NBOu}k&%>$[;DF$!.yȇD>t8ߐЛ<gp #9%- =Fr՟/qH3FQvaJ1੄Jʩ*I&UQ5$Q=5P#ɢ&jFo"Zڨ:h2.z<'WiTFiGH$Z 7-pK n%8z繍&n3p/sq,>)9% =wJ=OpRI&)9FKHB~o_7[:@Khw;+5u M ?̿ÿ˿ǿ??揢U??O'/S7i[;{G' 3 اF@]H `lW7AoENxEO[}pp26(== dJ,/Ԟ}2׋xʭyjIt鶨]]+33`REj-[ Ra Ukw+r1WPYVefԒckߥV@W6Ү26Ii`gK[V_zZnL.Vk}zV{q[XnaՅ ԮPyqUU8^Kkv[/F$V Z2oaOž*|嬥fV⟸pr ƾ⭳#;b'^Y8-stKboE$gm%lE^q\I%abGjFLUtY(t`ZI؅Pti˘D4(Vd|jZ kȺ"Mqzafƍ t c:d#^FE ڹ*uuA$;\UKkX3?rV15CmP>C-1A+= k|I-_֗(Vr(Q'"Sl*Z>"S_ #W-ӽVO^Yv],K/_jk͍@uR`»'S)|>E%CRpKHīZS%kxM*M a)¾DOdfȜ>ˇƴL,i1us:6 ~Imv~I 6 ޖ0L,1b)f z;4N8&||ڮn-gv6f2;2[]HrnU=Ƥov Zo%}?L6Mc?tob=ؽ6{mb!KzdK9tBWPJ \Vu1 ;kñ3VcA`]3;.2֕D^N\Kf X'u3g& 3gΜ5j標,͜u#̄ZonL7/BX,hn̪Y t fŒ n̋f^~0Cp7ސ`C3"-ƒōE{  *zG?ѣ' ~ s&}9\ G`*<`[>yhpzM ^x~ hcQ(&/C-#-I 8`x,.ΘSb:i ^5qod?06"%cExѻbbb! 9g˫Tj p`.<I-b cKpjПGaw%d.ٓ@ˁX4(?^' t87m:z2¹Íp3 K OᨒCMRkgO & "'{ Fk9b`tls8 I?$~YG^ /~q Nrz}]mѫSFc[\6(r¹Dl> 2=J\$"CI%!42 ϓd#?SJ t9JЏ)Ans[xs|  ?[, kH&#璍wCrB > ώ(C5 э~9'\1E >ͽ}ĩaZnh=nӵt4Dbt2TCCgL:!>HLn3ϾkLؽ}zP-2!Ց~ P?E߁Ng1[L{c6wEd<֒JI [H?wb҉+GwKUx˅kABײը#)0<}Os3iwpߙGDmU^/@ 'H''$w2!y%I0"1Qd|,66񘅨ޏEyȌ @~w/7mjBy<+hMAxgڈȃB|NR/CQ֠y` Z (3B w Q< }>N3 .B~?a<JnnqC[8P"~}{]o=AiQڵM_ $6JOf!MPlPh&1߃1 e"bm;^ /@| C@Q;2Fݑ[?C;DW:\P81Uq k#@y @ =_%췗W \+pW \+pW ? 쏹O@ݷHe D[%<+pJ]!cm nK\%/+@YIK Ϋ bBf9>?ƾ_I$7h?V}rbe1h2(t"Nn <%W5,>sp=!3'}.TXPg691K!j-4H)N,l=tU+ጒf%}{-IzODI ӔG;3{#jn] /:n'1{(4gȧɩ|7] z?G bD(AcxOq.l) v j!r:];x %?yqcq}Cz:Ged1ڞ1 \w&FnY 9,R{Ec%FfzȈ 32]وN zT#tNHP1wphHf߬##zF.TTЫoһa\f]k#9c'  k a7 05V>S-RcOOl'|*~BvCSC5iFPa |$.*/|Σe"l޽j6ZWvSDqxYo?{ {pYy]K޹PbD.G6^ Ѽ %?Gb$`0BYo[]:woo['~+KqwU>e5Cu_vo #;<=IDkd2b|lXAtOɛ@ޱWi}hAg%`j:w=t 'z>JK JK3p`Kqd3(t%Rc3-9#I@鑆kPr v!=SG -]( P%ab/Nf 7YNY]v-;TtAvCߤ]9<àq94 w{:vlYS60IeAaa?q8%gPLB,s@W_AɨG*-iѮE}W/UCcɯ;\:6A2PMC{EwK1BqFrb|aR<cr|8OPXT j onT`Fds;k M j83 )TЎ%Lɖ:D>2&l_AVmt.Yt,1\2_C)B1YCĦĒTgIѬ&q.,SFMSĂfW˨RuxlU$BPhhbi1 LQLVw?,gւ;\=x-=^'nX}tʾKιp揞ܿwJ}58:j历N r#ڝyPUVk 9e-lįdzq'= ::%(j5B }' :~Nd9_%fnKYڃRom%¹TrsZΛqI"TԎ.#fZx?ZRnhj,|PMșߵ1qe) @q"N'{֧OۥޚBuq:Ow)TڨbLA̧[t${)PҠUP ϭRvByfK]iMU&XՅUt*dZN8䭑*DVz AC(xjzB&5.B,nc$g޼8*dFոx :Q4hRdRY $@B[G6c[`*Ԃ2iǘǿ 1^n;R*SZgF}Øǟd\j+H)P:zz_9{W .^WxasO,lnчt5 &ת©:.b?l.Oj(ÆNGǣh KCIoӗJETDR.9=g)L罤ϗH5?$st@G;u*4/Vmh̃@`%$&t Cb,zF1 )UEzMw('ZĚHV3̚}awYZ Wmz1۫]v6 AYؚҍ>A/X3_^lpĨe%FEH-I -8 G4h=8fDN^քYMDz'P~zu{/q/4.ADd&fb5л"Z"ItA"( :xޤF;#F}1t3:rXެsGJZsmn -$VOP1:p\ˇݙ;]Lo02z 6]\!/爹 "S9>EU'mtuihF0.a?R " g)+!44# K^*`·4:e 4_lA^pַlLKu}Kt}Q3fѰ"ZE-UCrsQDת*9VMn麫"!3{rjL#vEC6DY>c yr$(Wȇ "I>1bG"J=\ӀWy^n:* # 32l |LƠ"# ٴO;3{ԙs}?V<;#mr` QU!%ɦYhDdtE 䢌ۖYq G"bv%) Ki) t>}= y P/JsH-w)&ڜtTBT|0'9A굜TQi4O@^ D/-HccX!^HPю,Ed 0 "r + E4rպ٩{VNO+}5zz5kǏ^B 3;޳hO7ix֦(-z߾z^Ԏ/vob89H '1C-W'eM74On(%6.aQ82p~le~IfK<ߥEWvE+n4H k#ufbm@0u "NѩۓDBJJP<1`t]E޼"#c%{`~i珻3ZZV;m.~`Ĭ,=Y~ޅY/zҪP*HBGIH9*);UdwL҆z L:Rh  18 :R2PșJbJNIHh\*;U}!m2%9L;s!P'gEa(B&l "`0ĥh?Ӭ ʝa[ovzƙF'$SK`䣽;W}pj8 :Bk ZKlx/I!1"3fZZ~!0CܠʈTk9-H5! Ȩ *aAp*Wtgjs94'\P̤rIAqLai e@F3 PlH*`2\wF |!pxc=IY1PseYlw09I1/$0QRrc.B?T_%H`u<$:ZiU[nHI4c>Z/pIӇD/Sٰ0`x "f9%d[2}:U:IiАì$5-#55 *yq,xΩ:ň&%bu7΀]~Jvp(tDcaX(J$rg8D`6_iK3NdH3YslA?0Ͱpk `pm`Eaq$H"F#L%rcj11eW{; =1E' R[_!1锖Đ8eI-ͥR[ Ԧ<Ӏ4Gb٨OO}&}cQz]MfC{\B }^[ҷb\5}*<_Q tVg A}МmHe:}TkgioҾ(?exyuM>0\m]#0,7 rsokppa^f-23^-*R:iC 5rVÃ̂JVTrIA<kA72[kBJt-;df`<ͬL֕b1befdtfg% ttѱ®X.i}VWY+]]#Ij̄qt' `+$rrTNV:$L>Vl )jn <ʶY$_"`{4ٺ=?E\KHђʵѯ?LG!zw.Q@ML8v!sлBGcGXOinm)tr 7mgP\f^( @7->h=;`:!4k Xfi9t(-+CzuL@f/lv\O1Z1M1G[EZu\Z${Ճ豄 /8FUQTxZ~zv1DKRzv;-J$ZQܺtrv7ӽ)]qbK\2)GV_z6RG~Eo˛䟔'eޫ}߽1 UM^o„=qN{}\lHV}%sU e3Vll66.= z)aJJL|D逳)P8 {Bv^oY!3@mC ,E闲0/b*=#]6VJĐ,G%%L K#Ef/&ZfpofDwf?.coY2t >ͪyTvk^MZ#B>/L-٦HU |`A)#|Rys/!go0B';$o 0lx[euu8l 9FVtɺj.U-(yޤl}[ޕأ~4[e#ΎjzZjnۃPXi(7R {ƽ! ɂ,MsW7M;iq}PRcIIl$p[fڐ1܏rɕ{0 T`I+BQՊr22ܗ |/RxYd<#㴎juquX<oDc.zI;ڻƻٻ{+ڋ-KsR?MFOzBKmw]`grZt"ḿߺE]}iF¶oF} C]; Xc^%ozF"3]!7&[xJ_e^ƃr!$jv\K=;wB(ƭn:c: Wt]nyl۵}״WbrD|Nq`F3 i?1XlTBQ F&[f3|.GTZ@m y/"\2v2"Ԟ j*ʴiFhKu`D8jJl)+)D^SWԪĈ 9@ԡW>[xPvmO9 9$? י\')Uu%T\CIZu5F2EuUyN߶elJHEp[MfTC5/$HA+!F0".ڙzmˇGϽr zu+NK7|Sgyy>7Lǃt!E0y TLNM .i0V ^r.mɞ&*a ::G@k3:v[TpΛ&S,^*j,swS= 1" mZB&TĮȅ wP`>~ u;]za"! ?̽.ЮW>0@C&:2IfRm8zᑧo]% +}b:evEt$D-beeM#pX->KUT+Q SoktZe?$fq+qErˍӍOS&oc@zmqgknK8mnioL[ӕDLB"+|\\GxI^2!#)IuzGk N`afŻd sk~~]IY?YEm{þ/;Tj|RQ}܅f#cu B]rhZ;7 d6ъ>ÇQ,LEY(&"+MZzty%ZROe[%d>rEZH#FӥeOȜ_{ ьCh_U 8ٹyb^iHbeRNX ډ#y74ӪUIK:uN;$2i!];IEBHctmsHo-Vq9>4:z,L'[\HifMY{kTy^U6@tcz0+- },XVV08ٯ@" }OMy`ɁɅ@qujUɌj=LH/JENN_fAN%NLJR)}:IVZ+Vs !#-md0--e@SX`,,, zF? F:&'::3332^*,TTLcd-E3`npL hPNWUPAQ`[V<s?Th}A{}&!hyK{yԻJضvܖ-b^gL-V{%7)]5_&>oeI{@?zFy}^;C'FQxھham/9.涝oNeꀐ+SiDN[^A[-F] }H]SHC&ϒJj討+}돤n/%!D AB`>d R2̮LC8=IĘ؎7Njfٍ$;1l,6V{vg[zUջu[n}^vr~]^bXM۫𪁞UV;\vWDUxA,j(Qlh]K`B jp\X-/_}o/\kWU갾#A c%K\&Mq-aVZr_{ڃJoYA=z^{75S4'}u#ם߀ͭ-]K׿QoxMּASW:]뵍cR=OGN !)+ # \_°K~~j~z1y%bREW|,0q} [/f8ЙY7SIڼyl.[&K)f ܼ{i_Gwje7g48 4'o4pu w O4܈~0ZawWiWVp U\t"(\l(! DX•9^jtY|)0vV4zDa2^%@ݯK|`z^L殀[wIZޠ3pJ-hU^iԓ(ȯT!1Of&vTg?TTU_;o۷t~ g_} qhW s]w*3n3rNQȒ[_ů~Gf>}Ź&aHZsmpx{țzKm^4Z]+@X@-vppG1nQI^]]iug=`Br3 N6vTg iSdgޅ!V_ mh{z8k(U%!%G"SK|TdG@Wwܦn AJK_wNx\/cǹc~smz sWCYd廙kŲAn:A&},1l~Q_Yu ^~)+q+i􆫼ʪFJɐ&VGlm#(-0UGjv6/E6oR*U/S+{Nb5nM,H}HbU0z?.TrTj?Y^ts{U53?;_yTѲ78oǿp[ ?בDY4$g_gw4YNTTۆ{oWig+^:=Nk4jT%cK%5&M[I6T$t^#ĵ0޷ IJ=5.t!ҹz?qs/?4npTwvJ6Y JΨ]oUuulP[pbdG)~0) n}t_tz+phJRNumkWVϯ/߬٩=ƵNX{_XvNDȝ_yoaI M vnӻ,]%i^iYxGtxW60fve "};BW J 2+Հ0u5%O6]M9BμIT(ΎRĹFjܡ@ [SܞիapE;WV#i:Pw/`nDߡ~H`XOj>U'X]#8 Nb= 'F_3/F"A{#6[m,~[.QW[S66tn;#-<ӗ_>dpwwO|~Go~*Pկ}iEw} ݽ~A\{gpXiN" #z5ZIV*6`I{v{ouny71ˠ A,HFtZDHM>1Y^tTs?.5KRT*+UjaLx2⺃]WrG\klfѹ}0>c52UX[hC8RyU`N+v|C x?Lz0\Nb Dz ]0C QIg sfu{.@ò]RTgr{"~`v͊[&Ap(XO˗SLӢOyBvSFNbܾ7&tAJq\xٹ+d=4p\羇?)?>=#?6 N_UUaoUTcV,|.h$SJ>z9Xy%̝9gA.e$|!Ҡ;4HQ.b (jѥ<mZ7ڲojFAPhFgaE mjGhGZvv(i"ZѲD;mbh h i Hew6ZqH5-[9.j(1 GK!?ܐs=>y<ݹY^.`a/ !>tס-N0|5h=egМNgYt35䏣;vz[ʹq︇{i9>jGȉ̯I1Hn{(Tikm/g7 z0ȣ6,9ľ m8DAmm}(*v)h"8btFH D|HM$i\鋬G"ѾPw n~J{W k-lhe ?dצ@5|Cu" Ts C66jFFk:0[}6rd]] 6ζ6%B)сũ">\85HSS ie2lߪH+hV.o<`3szb 7& S±MK `h $إ)|MٗЯM؅s+p h[sq@sƹYW6r/ 2ݝ~3!* ϳ?) `*̣"-( P% C*l@k*lD+*l3JU؂̹VS\Tm6 = N?,0FfEjPa-J5E8ZdUXl ֣6 6" ]1\w 5AMYĿ`A|:~z(lK62b0d`Md`&#31ɈLF f2b0l*j- [m~i!|Ra\%y2+(YUS]P 7R(Lb(,KQ%ז p  zh›P MUwiIP % GJ|$L'i f嵢.kATIAqC2pGj :@*Fow5CHrfIYa#iCJ)H's鬕Ӕj湰LRBiOcZɴbBS2W&i"мL7ra&DT+T_4mR!ҋS.=ݥ6P#sVfi,P8 w$}Ғ}c{WJmXʬJZ卪CtbRVaU2S*kISLkY:AIJ-{ݲ !%IKI &~AJP:#:F[Yd8E%}{r`" 4.[־rnU,>*b[1E3̊Pzk7WDYV#:tcmb!=NiZ3Bcfۦd.w2i_Qj6p|>IU>X[Yj9x=/mˋK&Xꛥv ǙQ\^PB= SI0K-:՟imgs>ϿnYTSn_ܳp%qp:>+(;&ʦT* IJ_*=JDzԔ2iVcا 1e{*9CR2ʆ)ղ feM2lKOd3ʶx&>͑Z՗I'ies|vՁx:CH5w)#T&5mIҷ=VA7PN*;ұd,OIݰJ:>dRFlĩ,4o޲c5;6l٬lPnзnuʚ[iVմc"Q9NNAR|x:6=qPMAL\>L͐#23S B*7!DbJ21x:Oe]m"v I!g2qt\'XZM#Ae,,+ee0 F=LHC5SSer^Y pL9KĆPL&-ݬ N% mKu`q H2N$FmjAH cҰZkUgM)!(']EQ) JGk粖qp#$CرCXkE4N ggg'sIMOAMGl6O3q12g6 "J+TI7ݴTəY@#ʙ@-׌Pہ4qePǓi 7?+B=12 NdG&QZR֐(9X8 dlfnz2aZDPLvz&Kk!J  FG&heFS#3DT óDv2N&̞Ǝl3ysƓ,.* FiRΏw< zl1m5W#,,qϡֲAYdY/PChB8,_X01|k5BpG9(\sWO.х|˄7t@ 2֞I̎/kwܠ%EݠC@΅>#Bϟ؃}Q3xF.plCK3OR|?X4lKh:_ \SW඲.@/yo#p)Hi&7z\%p)JG**Km6wq-Z# Kv_"_BR|A%O\pPڵ"/Mm$H>B _(~|:+}.$Fdw=p\,H,Ki eK), oY: ;/цKk"v**{6E`Q;1*w"^TWD62N}T lT:J1aգeP#tYW |`Zs2*tk)-R2#Ͻ>> stream x]n0 yChW !u8F41]x"A9QUj=> F9/pՆ%+-B}kY4zk (8>Cvn⫽.Ģwis嫯 ܬ1+K ^[#kOyT' \ 'F FJp+J^*/բ qʙg<'\NDR&蔥Le76=I).ّΈ&P;0xm+n|˛sax8ygU ,٠sendstream endobj 16 0 obj << /Type /XRef /Length 73 /Filter /FlateDecode /DecodeParms << /Columns 5 /Predictor 12 >> /W [ 1 3 1 ] /Size 17 /ID [<162ada9ceffc3ae9c2dd5a63d76ae007><162ada9ceffc3ae9c2dd5a63d76ae007>] >> stream xcb&FY+ ȿf "y 2 "9$E0DJNLH3[ endstream endobj startxref 216 %%EOF flare-engine-1.14/distribution/macos/flare.sh000077700000000000000000000000001434514645000242412../linux/flare.shustar00rootroot00000000000000flare-engine-1.14/distribution/macos/package_osx.sh000077500000000000000000000064051434514645000224520ustar00rootroot00000000000000#!/usr/bin/env bash -e cd "`dirname "$0"`" cd ../../ FLARE_EXE=$1 FLARE_DEPS_SRC="http" FLARE_GAME="" if [ -z "${FLARE_EXE}" ]; then echo "usage: $0 " exit 1 fi if [ ! -f ${FLARE_EXE} ]; then echo "no Flare executable found at ${FLARE_EXE}. Please follow README in order to build Flare engine first" exit 1 fi if [ `otool -L ${FLARE_EXE} | egrep libSDL2 | wc -l` -lt 1 ]; then echo "invalid Flare executable" exit 1 fi if [ "$2" != "" ]; then FLARE_DEPS_SRC=$2 fi if [ ${FLARE_DEPS_SRC} == "http" ]; then echo "download dependencies from website" elif [ ${FLARE_DEPS_SRC} == "homebrew" ]; then echo "copy dependencies from homebrew" else echo "usage: $0 " exit 1 fi if [ "$3" != "" ]; then FLARE_GAME=$3 fi DST=/tmp/___flare.build rm -fr ${DST} && mkdir -p ${DST} cp -r RELEASE_NOTES.txt \ README.engine.md \ CREDITS.engine.txt \ COPYING \ ${FLARE_EXE} \ mods ${DST} if [ ${FLARE_DEPS_SRC} == "http" ]; then #feel free to build dependencies by yourself btw wget 'http://files.ruads.org/flare_osx_dependencies.tar.gz' -P ${DST} tar -zxf ${DST}/flare_osx_dependencies.tar.gz -C ${DST} rm -f ${DST}/flare_osx_dependencies.tar.gz elif [ ${FLARE_DEPS_SRC} == "homebrew" ]; then LIB=${DST}/lib mkdir ${LIB} # SDL2 cp /usr/local/opt/sdl2/COPYING.txt ${LIB}/SDL2-COPYING.txt cp /usr/local/opt/sdl2/README.txt ${LIB}/SDL2-README.txt cp /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib ${LIB} cp /usr/local/opt/sdl2_image/lib/libSDL2_image-2.0.0.dylib ${LIB} cp /usr/local/opt/sdl2_mixer/lib/libSDL2_mixer-2.0.0.dylib ${LIB} cp /usr/local/opt/sdl2_ttf/lib/libSDL2_ttf-2.0.0.dylib ${LIB} # VORBIS cp /usr/local/opt/libvorbis/COPYING ${LIB}/VORBIS-COPYING cp /usr/local/opt/libvorbis/lib/libvorbis.0.dylib ${LIB} cp /usr/local/opt/libvorbis/lib/libvorbisenc.2.dylib ${LIB} cp /usr/local/opt/libvorbis/lib/libvorbisfile.3.dylib ${LIB} # OGG cp /usr/local/opt/libogg/COPYING ${LIB}/OGG-COPYING cp /usr/local/opt/libogg/lib/libogg.0.dylib ${LIB} # PNG cp /usr/local/opt/libpng/lib/libpng16.16.dylib ${LIB} # Verify all homebrew deps using using otool for DYLIB in ${LIB}/*.dylib; do #echo "dylib: ${DYLIB}" for LINE in $(otool -L ${DYLIB}); do #echo "line: ${LINE}" if [[ $LINE == *"/usr/local/opt/"* ]]; then NEED=$(basename ${LINE}) #echo "${DYLIB} need: ${NEED}" FILE="${LIB}/${NEED}" if [ ! -f "${FILE}" ]; then echo "${NEED} not found, copying" cp ${LINE} ${LIB} fi fi done done else echo "'${FLARE_DEPS_SRC}' unknown dependency source" exit 1 fi if [ "${FLARE_GAME}" != "" ]; then FLARE_ENGINE_MOD_LIST=mods/mods.txt FLARE_GAME_MOD=${FLARE_GAME}/mods while IFS= read -r MOD; do MOD_DIR=${FLARE_GAME_MOD}/${MOD} if [ "${MOD}" != "" ] && [ -d "${MOD_DIR}" ]; then cp -r ${MOD_DIR} ${DST}/mods echo "copied ${MOD}" fi done < "${FLARE_ENGINE_MOD_LIST}" cp ${FLARE_GAME}/CREDITS.txt ${DST} cp ${FLARE_GAME}/LICENSE.txt ${DST} fi echo '#!/bin/sh' >> ${DST}/start.sh echo 'cd "$(dirname "${BASH_SOURCE[0]}")"' >> ${DST}/start.sh echo 'DYLD_LIBRARY_PATH=./lib ./flare' >> ${DST}/start.sh chmod +x ${DST}/start.sh echo "packaging" tar -zcf flare_osx.tar.gz -C ${DST} . rm -fr ${DST} echo "done" flare-engine-1.14/distribution/nsis_script.nsi000066400000000000000000000163771434514645000216110ustar00rootroot00000000000000; Flare NSIS script ; ; IMPORTANT! This script is not to be run in this distribution directory ; It is to be copied to an empty directory along with the following files: ; ; flare.exe ; *.dll (These are the SDL libs, and are not included here) ; COPYING ; CREDITS.txt (part of flare-game) ; CREDITS.engine.txt ; LICENSE.txt (part of flare-game) ; README.md (part of flare-game) ; README.engine.md ; RELEASE_NOTES.txt ; mods/mods.txt ; mods/default/ ; mods/fantasycore/ (part of flare-game) ; mods/empyrean_campaign/ (part of flare-game) ; mods/centered_statbars/ (part of flare-game) ; distribution/Flare.ico ; distribution/Flare.bmp ; ;-------------------------------- ;-------------------------------- ;Include Modern UI !include "MUI2.nsh" !include "x64.nsh" !define MUI_ICON "Flare.ico" !define MUI_HEADERIMAGE !define MUI_HEADERIMAGE_BITMAP "Flare.bmp" !define MUI_HEADERIMAGE_RIGHT !define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_RUN_TEXT "Launch Flare" !define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink" !define MUI_FINISHPAGE_NOAUTOCLOSE Function LaunchLink SetOutPath $INSTDIR ExecShell "" "$INSTDIR\flare.exe" FunctionEnd ; The name of the installer Name "Flare" ; The file to write OutFile "flare_install.exe" ; The default installation directory InstallDir $PROGRAMFILES\Flare ; Registry key to check for directory (so if you install again, it will ; overwrite the old one automatically) InstallDirRegKey HKLM "Software\Flare" "Install_Dir" ; Request application privileges for Windows Vista RequestExecutionLevel admin Function .onInit ${If} ${RunningX64} StrCpy $INSTDIR "$PROGRAMFILES64\Flare" ${Else} StrCpy $INSTDIR "$PROGRAMFILES\Flare" ${EndIf} FunctionEnd ;-------------------------------- ;Pages ; !insertmacro MUI_PAGE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ;-------------------------------- ;Languages !insertmacro MUI_LANGUAGE "English" ;-------------------------------- ; The "" makes the section hidden. Section "" SecUninstallPrevious Call UninstallPrevious SectionEnd Function UninstallPrevious ; We need to use INSTDIR for the uninstaller, so save the current value here StrCpy $R0 $INSTDIR ; try old 32-bit location ${If} ${RunningX64} SetRegView 32 ReadRegStr $R1 HKLM "SOFTWARE\Flare" "Install_Dir" ${If} $R1 != "" DetailPrint "Removing previous installation: $R1" StrCpy $INSTDIR "$R1" Call UninstallAll ${EndIf} SetRegView 64 ${Endif} ReadRegStr $R1 HKLM "SOFTWARE\Flare" "Install_Dir" ${If} $R1 != "" DetailPrint "Removing previous installation: $R1" StrCpy $INSTDIR "$R1" Call UninstallAll ${EndIf} StrCpy $INSTDIR $R0 FunctionEnd ; The stuff to install SectionGroup "!Flare (64-bit)" Section "Flare engine" SecEngine SectionIn RO ; Set output path to the installation directory. SetOutPath $INSTDIR ; Put file there File "flare.exe" File "*.dll" File "COPYING" File "CREDITS.txt" File "CREDITS.engine.txt" File "LICENSE.txt" File "README.md" File "README.engine.md" File "RELEASE_NOTES.txt" CreateDirectory "$INSTDIR\mods\default" SetOutPath "$INSTDIR\mods" File "mods\mods.txt" File /r "mods\default" ; Write the installation path into the registry WriteRegStr HKLM SOFTWARE\Flare "Install_Dir" "$INSTDIR" ; Write the uninstall keys for Windows WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Flare" "DisplayName" "Flare" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Flare" "UninstallString" '"$INSTDIR\uninstall.exe"' WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Flare" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Flare" "NoRepair" 1 WriteUninstaller "uninstall.exe" ; Create the user mods directory so we can link to it in the start menu CreateDirectory "$APPDATA\flare\userdata\mods" SectionEnd Section "Flare: Empyrean Campaign" SecGame SetOutPath $INSTDIR CreateDirectory "$INSTDIR\mods\fantasycore" CreateDirectory "$INSTDIR\mods\empyrean_campaign" SetOutPath "$INSTDIR\mods" File /r "mods\fantasycore" File /r "mods\empyrean_campaign" File /r "mods\centered_statbars" SectionEnd ; Optional section (can be disabled by the user) Section "Desktop Shortcut" SetOutPath $INSTDIR CreateShortcut "$DESKTOP\Flare.lnk" "$INSTDIR\flare.exe" "" "$INSTDIR\flare.exe" 0 SectionEnd ; Optional section (can be disabled by the user) Section "Start Menu Shortcuts" SetOutPath $INSTDIR CreateDirectory "$SMPROGRAMS\Flare" CreateShortcut "$SMPROGRAMS\Flare\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 CreateShortcut "$SMPROGRAMS\Flare\Flare.lnk" "$INSTDIR\flare.exe" "" "$INSTDIR\flare.exe" 0 CreateShortcut "$SMPROGRAMS\Flare\Flare Mods.lnk" "$APPDATA\flare\userdata\mods" "" "$APPDATA\flare\userdata\mods" 0 SectionEnd SectionGroupEnd SectionGroup "Flare (32-bit)" ; Optional section (can be disabled by the user) Section /o "Flare engine" SecEngine32 SetOutPath $INSTDIR CreateDirectory "$INSTDIR\x86" File /r "x86" SectionEnd ; Optional section (can be disabled by the user) Section /o "Desktop Shortcut" SetOutPath $INSTDIR CreateShortcut "$DESKTOP\Flare (32-bit).lnk" "$INSTDIR\x86\flare.exe" "--data-path=$\"$INSTDIR$\"" "$INSTDIR\x86\flare.exe" 0 SectionEnd ; Optional section (can be disabled by the user) Section /o "Start Menu Shortcuts" SetOutPath $INSTDIR CreateDirectory "$SMPROGRAMS\Flare" CreateShortcut "$SMPROGRAMS\Flare\Flare (32-bit).lnk" "$INSTDIR\x86\flare.exe" "--data-path=$\"$INSTDIR$\"" "$INSTDIR\x86\flare.exe" 0 SectionEnd SectionGroupEnd ;-------------------------------- ;Descriptions ;Language strings LangString DESC_SecEngine ${LANG_ENGLISH} "The Flare engine without any game or mods." LangString DESC_SecEngine32 ${LANG_ENGLISH} "The Flare engine (32-bit version)." LangString DESC_SecGame ${LANG_ENGLISH} "The Empyrean Campaign game developed by the Flare team." ;Assign language strings to sections !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN !insertmacro MUI_DESCRIPTION_TEXT ${SecEngine} $(DESC_SecEngine) !insertmacro MUI_DESCRIPTION_TEXT ${SecEngine32} $(DESC_SecEngine32) !insertmacro MUI_DESCRIPTION_TEXT ${SecGame} $(DESC_SecGame) !insertmacro MUI_FUNCTION_DESCRIPTION_END ;-------------------------------- ; Uninstaller !macro UninstallAll un Function ${un}UninstallAll ; Remove registry keys DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Flare" DeleteRegKey HKLM SOFTWARE\Flare ; Remove files and uninstaller Delete "$INSTDIR\flare.exe" Delete "$INSTDIR\COPYING" Delete "$INSTDIR\CREDITS.engine.txt" Delete "$INSTDIR\CREDITS.txt" Delete "$INSTDIR\README.engine.md" Delete "$INSTDIR\README.md" Delete "$INSTDIR\RELEASE_NOTES.txt" Delete "$INSTDIR\LICENSE.txt" Delete "$INSTDIR\*.dll" Delete "$INSTDIR\uninstall.exe" RMDir /r "$INSTDIR\mods\" RMDir /r "$INSTDIR\x86\" ; Remove shortcuts, if any Delete "$SMPROGRAMS\Flare\*.*" Delete "$DESKTOP\Flare.lnk" Delete "$DESKTOP\Flare (32-bit).lnk" ; Remove directories used RMDir "$SMPROGRAMS\Flare" RMDir "$INSTDIR" FunctionEnd !macroend !insertmacro UninstallAll "" !insertmacro UninstallAll "un." Section "Uninstall" Call un.UninstallAll SectionEnd flare-engine-1.14/docs/000077500000000000000000000000001434514645000147315ustar00rootroot00000000000000flare-engine-1.14/docs/attribute-reference.html000066400000000000000000004146021434514645000215650ustar00rootroot00000000000000 FLARE Documentation - Attribute Reference

    Attribute description

    Attribute descriptions are in the following format: section.attributename, valuetype, short attribute description

    This reference is generated from in-source documentation, so please do not edit it directly.

    Valuetype syntax

    [val1, ...], Multiple values between a set of square brackets denotes possible types/values for a single field.

    "...", Anything between double quotes is a string literal. This is mostly used in conjunction with the above square brackets syntax.

    list(...), Lists are a series of values that can repeat on a single line, separated by commas/semicolons. Example of list(int, bool): "1,false;2,true"

    repeatable(...), Repeatable keys can be used for multiple lines. An example of this would be an item with multiple "bonus" lines.

    List of available valuetypes

    bool, a string value of true or false

    int, a signed integer value

    string, a text string value

    float, a floating point number

    item_id, specifies an integer item identifer (greater than 0)

    power_id, specifies an integer power identifer (greater than 0)

    icon_id, specifies an integer icon identifer (greater than or equal to 0)

    point, defined as: int, int : X, Y

    rectangle, defined as: int, int, int, int : X, Y, Width, Height

    filename, a string path to a file relative to the base of the mod directory (e.g. "animations/hero.txt")

    color, defined as: int, int, int : Red, Green, Blue

    predefined_string, same as a string, but uses a value defined elsewhere

    alignment, defined as: ["topleft", "top", "topright", "left", "center", "right", "bottomleft", "bottom", "bottomright", "frame_topleft", "frame_top", "frame_topright", "frame_left", "frame_center", "frame_right", "frame_bottomleft", "frame_bottom", "frame_bottomright"]

    direction, defined as: ["N", "NE", "E", "SE", "S", "SW", "W", "NW", int]. If defined as an integer, the value must be between 0-7 inclusive, mapping to ["W", "NW", "N", "NE", "E", "SE", "S", "SW"] respectively.

    duration, durations can be specified in seconds and milliseconds with integer suffix s, ms (eg. 20s, 20000ms)

    label, defined as: "hidden" or int, int, ["left", "right", "center"], ["top", "center", "bottom"], string : X, Y, Justify, Vertical Align, Font style. The font style can be any style defined in engine/font_settings.txt.

    loot, defined as: filename or ["currency", item_id], ["fixed", float], int, int : Item, Drop chance, Min quantity, Max quantity. There is a limitation when defining as part of a list(...): filenames can only be used in the first list element.

    version, defined as: a string of three numbers, separated by dots (e.g. "1.2.03")

    raw, This is plain text, including line breaks. It is used only for map layer data.

    stat_id, a string that can be any of the following: A base stat (e.g. hp or avoidance); A min or max damage type from engine/damage_types.txt (e.g. dmg_melee_min or dmg_ment_max); An elemental resistance from engine/elements.txt. To use an element as a resistance, _resist is appened to the element id (e.g. fire_resist)


    AnimationSet

    Description of animations in animations/

    image | filename, string : Filename, ID | Filename of sprite-sheet image along with an identifier string. The identifier string may be omitted if there is only a single image.

    render_size | int, int : Width, Height | Width and height of animation.

    render_offset | int, int : X offset, Y offset | Render x/y offset.

    blend_mode | ["normal", "add"] | The type of blending used when rendering this animation.

    alpha_mod | int | Changes the default alpha of this animation. 255 is fully opaque.

    color_mod | color | Changes the default color mod of this animation. "255,255,255" is no color mod.

    animation.position | int | Number of frames to the right to use as the first frame. Unpacked animations only.

    animation.frames | int | The total number of frames

    animation.duration | duration | The duration of the entire animation in 'ms' or 's'.

    animation.type | ["play_once", "back_forth", "looped"] | How to loop (or not loop) this animation.

    animation.active_frame | [list(int), "all"] | A list of frames marked as "active". Also, "all" can be used to mark all frames as active.

    animation.frame | int, int, int, int, int, int, int, int, string : Index, Direction, X, Y, Width, Height, X offset, Y offset, Image ID | A single frame of a compressed animation. The image ID may be omitted, in which case the first available image will be used.


    Avatar: Step sounds

    Description of items/step_sounds.txt

    id | string | An identifier name for a set of step sounds.

    step | filename | Filename of a step sound effect.


    CombatText

    Description of engine/combat_text.txt

    duration | duration | Duration of the combat text in 'ms' or 's'.

    speed | float | Motion speed of the combat text.

    offset | int | The vertical offset for the combat text's starting position.

    fade_duration | duration | How long the combat text will spend fading out in 'ms' or 's'.


    CursorManager

    Description of engine/mouse_cursor.txt

    normal | filename | Filename of an image for the normal cursor.

    interact | filename | Filename of an image for the object interaction cursor.

    talk | filename | Filename of an image for the NPC interaction cursor.

    attack | filename | Filename of an image for the cursor when attacking enemies.

    lowhp_normal | filename | Filename of an image for the normal cursor when health is low.

    lowhp_interact | filename | Filename of an image for the object interaction cursor when health is low.

    lowhp_talk | filename | Filename of an image for the NPC interaction cursor when health is low.

    lowhp_attack | filename | Filename of an image for the cursor when attacking enemies and health is low.


    EnemyGroupManager

    Description of enemies in enemies/

    level | int | Level of the enemy

    rarity | ["common", "uncommon", "rare"] | Enemy rarity

    categories | list(predefined_string) | Comma separated list of enemy categories


    EngineSettings: Misc

    Description of engine/misc.txt

    save_hpmp | bool | When saving the game, keep the hero's current HP and MP.

    corpse_timeout | duration | Duration that a corpse can exist on the map in 'ms' or 's'. Use 0 to keep corpses indefinitely.

    sell_without_vendor | bool | Allows selling items when not at a vendor via CTRL-Click.

    aim_assist | int | The pixel offset for powers that use aim_assist.

    window_title | string | Sets the text in the window's titlebar.

    save_prefix | string | A string that's prepended to save filenames to prevent conflicts between mods.

    sound_falloff | int | The maximum radius in tiles that any single sound is audible.

    party_exp_percentage | float | The percentage of XP given to allies.

    enable_ally_collision | bool | Allows allies to block the player's path.

    enable_ally_collision_ai | bool | Allows allies to block the path of other AI creatures.

    currency_id | item_id | An item id that will be used as currency.

    interact_range | float | Distance where the player can interact with objects and NPCs.

    menus_pause | bool | Opening any menu will pause the game.

    save_onload | bool | Save the game upon changing maps.

    save_onexit | bool | Save the game upon quitting to the title screen or desktop.

    save_pos_onexit | bool | If the game gets saved on exiting, store the player's current position instead of the map spawn position.

    save_oncutscene | bool | Saves the game when triggering any cutscene via an Event.

    save_onstash | [bool, "private", "shared"] | Saves the game when changing the contents of a stash. The default is true (i.e. save when using both stash types). Use caution with the values "private" and false, since not saving shared stashes exposes an item duplication exploit.

    save_anywhere | bool | Saves the game when using a button.

    camera_speed | float | Modifies how fast the camera moves to recenter on the player. Larger values mean a slower camera. Default value is 10.

    save_buyback | bool | Saves the vendor buyback stock whenever the game is saved.

    keep_buyback_on_map_change | bool | If true, NPC buyback stocks will persist when the map changes. If false, save_buyback is disabled.

    sfx_unable_to_cast | filename | Sound to play when the player lacks the MP to cast a power.

    combat_aborts_npc_interact | bool | If true, the NPC dialog and vendor menus will be closed if the player is attacked.

    fogofwar | int | Set the fog of war type. 0-disabled, 1-minimap, 2-tint, 3-overlay.

    save_fogofwar | bool | If true, the fog of war layer keeps track of the progress.

    mouse_move_deadzone | float, float : Deadzone while moving, Deadzone while not moving | Adds a deadzone circle around the player to prevent erratic behavior when using mouse movement. Ideally, the deadzone when moving should be less than the deadzone when not moving. Defaults are 0.25 and 0.75 respectively.


    EngineSettings: Resolution

    Description of engine/resolutions.txt

    menu_frame_width | int | Width of frame for New Game, Configuration, etc. menus.

    menu_frame_height | int | Height of frame for New Game, Configuration, etc. menus.

    icon_size | int | Size of icons.

    required_width | int | Minimum window/screen resolution width.

    required_height | int | Minimum window/screen resolution height.

    virtual_height | list(int) | A list of heights (in pixels) that the game can use for its actual rendering area. The virtual height chosen is based on the current window height. The width will be resized to match the window's aspect ratio, and everything will be scaled up to fill the window.

    virtual_dpi | float | A target diagonal screen DPI used to determine how much to scale the internal render resolution.

    ignore_texture_filter | bool | If true, this ignores the "Texture Filtering" video setting and uses only nearest-neighbor scaling. This is good for games that use pixel art assets.


    EngineSettings: Gameplay

    Description of engine/gameplay.txt

    enable_playgame | bool | Enables the "Play Game" button on the main menu.


    EngineSettings: Combat

    Description of engine/combat.txt

    absorb_percent | float, float : Minimum, Maximum | Limits the percentage of damage that can be absorbed. A max value less than 100 will ensure that the target always takes at least 1 damage from non-elemental attacks.

    resist_percent | float, float : Minimum, Maximum | Limits the percentage of damage that can be resisted. A max value less than 100 will ensure that the target always takes at least 1 damage from elemental attacks.

    block_percent | float, float : Minimum, Maximum | Limits the percentage of damage that can be absorbed when the target is in the 'block' animation state. A max value less than 100 will ensure that the target always takes at least 1 damage from non-elemental attacks.

    avoidance_percent | float, float : Minimum, Maximum | Limits the percentage chance that damage will be avoided.

    miss_damage_percent | float, float : Minimum, Maximum | The percentage of damage dealt when a miss occurs.

    crit_damage_percent | float, float : Minimum, Maximum | The percentage of damage dealt when a critical hit occurs.

    overhit_damage_percent | float, float : Minimum, Maximum | The percentage of damage dealt when an overhit occurs.

    resource_round_method | [none, round, floor, ceil] | Rounds the numbers for most combat events that affect HP/MP. For example: damage taken, HP healed, MP consumed. Defaults to 'round'.


    EngineSettings: Elements

    Description of engine/elements.txt

    element.id | string | An identifier for this element. When used as a resistance, "_resist" is appended to the id. For example, if the id is "fire", the resist id is "fire_resist".

    element.name | string | The displayed name of this element.


    EngineSettings: Equip flags

    Description of engine/equip_flags.txt

    flag.id | string | An identifier for this equip flag.

    flag.name | string | The displayed name of this equip flag.


    EngineSettings: Primary Stats

    Description of engine/primary_stats.txt

    stat.id | string | An identifier for this primary stat.

    stat.name | string | The displayed name of this primary stat.


    EngineSettings: Classes

    Description of engine/classes.txt

    name | string | The displayed name of this class.

    description | string | A description of this class.

    currency | int | The amount of currency this class will start with.

    equipment | list(item_id) | A list of items that are equipped when starting with this class.

    carried | list(item_id) | A list of items that are placed in the normal inventorty when starting with this class.

    primary | predefined_string, int : Primary stat name, Default value | Class starts with this value for the specified stat.

    actionbar | list(power_id) | A list of powers to place in the action bar for the class.

    powers | list(power_id) | A list of powers that are unlocked when starting this class.

    campaign | list(string) | A list of campaign statuses that are set when starting this class.

    power_tree | string | Power tree that will be loaded by MenuPowers

    hero_options | list(int) | A list of indicies of the hero options this class can use.

    default_power_tab | int | Index of the tab to switch to when opening the Powers menu


    EngineSettings: Damage Types

    Description of engine/damage_types.txt

    damage_type.id | string | The identifier used for Item damage_type and Power base_damage.

    damage_type.name | string | The displayed name for the value of this damage type.

    damage_type.name_min | string | The displayed name for the minimum value of this damage type.

    damage_type.name_max | string | The displayed name for the maximum value of this damage type.

    damage_type.description | string | The description that will be displayed in the Character menu tooltips.

    damage_type.min | string | The identifier used as a Stat type and an Effect type, for the minimum damage of this type.

    damage_type.max | string | The identifier used as a Stat type and an Effect type, for the maximum damage of this type.


    EngineSettings: Death penalty

    Description of engine/death_penalty.txt

    enable | bool | Enable the death penalty.

    permadeath | bool | Force permadeath for all new saves.

    currency | float | Remove this percentage of currency.

    xp_total | float | Remove this percentage of total XP.

    xp_current_level | float | Remove this percentage of the XP gained since the last level.

    random_item | bool | Removes a random item from the player's inventory.


    EngineSettings: Tooltips

    Description of engine/tooltips.txt

    tooltip_offset | int | Offset in pixels from the origin point (usually mouse cursor).

    tooltip_width | int | Maximum width of tooltip in pixels.

    tooltip_margin | int | Padding between the text and the tooltip borders.

    npc_tooltip_margin | int | Vertical offset for NPC labels.

    tooltip_background_border | int | The pixel size of the border in "images/menus/tooltips.png".

    tooltip_visible_max | int | The maximum number of floating tooltips on screen at once. Defaults to 3.


    EngineSettings: Loot

    Description of engine/loot.txt

    tooltip_margin | int | Vertical offset of the loot tooltip from the loot itself.

    autopickup_currency | bool | Enable autopickup for currency

    autopickup_range | float | Minimum distance the player must be from loot to trigger autopickup.

    currency_name | string | Define the name of currency in game

    vendor_ratio_buy | float | Global multiplier for item prices on the "Buy" tab of vendors. Defaults to 1.0.

    vendor_ratio_sell | float | Global multiplier for the currency gained when selling an item and the price of items on the "Sell" tab of vendors. Defaults to 0.25.

    vendor_ratio_sell_old | float | Global multiplier for item prices on the "Sell" tab of vendors after the player has left the map or quit the game. Falls back to the value of vendor_ratio_sell by default.

    sfx_loot | filename | Filename of a sound effect to play for dropping loot.

    drop_max | int | The maximum number of random item stacks that can drop at once

    drop_radius | int | The distance (in tiles) away from the origin that loot can drop

    hide_radius | float | If an entity is within this radius relative to a piece of loot, the label will be hidden unless highlighted with the cursor.

    vendor_ratio | int | (Deprecated in v1.12.85; use 'vendor_ratio_sell' instead) Percentage of item buying price to use as selling price. Also used as the buyback price until the player leaves the map.

    vendor_ratio_buyback | int | (Deprecated in v1.12.85; use 'vendor_ratio_sell_old' instead) Percentage of item buying price to use as the buying price for previously sold items.


    EngineSettings: Tileset config

    Description of engine/tileset_config.txt

    tile_size | int, int : Width, Height | The width and height of a tile.

    orientation | ["isometric", "orthogonal"] | The perspective of tiles; isometric or orthogonal.


    EngineSettings: Widgets

    Description of engine/widget_settings.txt

    misc.selection_rect_color | color, int : Color, Alpha | Color of the selection rectangle when navigating widgets without a mouse.

    misc.selection_rect_corner_size | int | Size of the corners on the selection rectangle shown when navigating widgets. Set to 0 to drawn the entire rectangle instead.

    misc.colorblind_highlight_offset | int, int : X offset, Y offset | The pixel offset of the '*' marker on highlighted icons in colorblind mode.

    tab.padding | int, int : Left/right padding, Top padding | The pixel padding around tabs. Controls how the left and right edges are drawn.

    slot.quantity_label | label | Setting for the slot quantity text.

    slot.quantity_color | color | Text color for the slot quantity text.

    slot.quantity_bg_color | color, int : Color, Alpha | If a slot has a quantity, a rectangle filled with this color will be placed beneath the text.

    slot.hotkey_label | label | Setting for the slot hotkey text.

    slot.hotkey_color | color | Text color for the slot hotkey text.

    slot.hotkey_bg_color | color, int : Color, Alpha | If a slot has a hotkey, a rectangle filled with this color will be placed beneath the text.

    listbox.text_margin | int, int : Left margin, Right margin | The pixel margin to leave on the left and right sides of listbox element text.

    horizontal_list.text_width | int | The pixel width of the text area that displays the currently selected item. Default is 150 pixels;

    scrollbar.bg_color | color, int : Color, Alpha | The background color for the entire scrollbar.


    EngineSettings: XP table

    Description of engine/xp_table.txt

    level | int, int : Level, XP | The amount of XP required for this level.


    EngineSettings: Number Format

    Description of engine/number_format.txt

    player_statbar | int | Number of digits after the decimal place to display for values in the player's statbars (HP/MP).

    enemy_statbar | int | Number of digits after the decimal place to display for values in the enemy HP statbar.

    combat_text | int | Number of digits after the decimal place to display for values in combat text.

    character_menu | int | Number of digits after the decimal place to display for values in the 'Character' menu.

    item_tooltips | int | Number of digits after the decimal place to display for values in item tooltips.

    power_tooltips | int | Number of digits after the decimal place to display for values in power tooltips (except durations).

    durations | int | Number of digits after the decimal place to display for durations.

    death_penalty | int | Number of digits after the decimal place to display for death penalty messages.


    EngineSettings: Resource Stats

    Description of engine/resource_stats.txt

    resource_stat.stat_base | string | The identifier used for the base ("Max") stat.

    resource_stat.stat_regen | string | The identifier used for the regeneration stat.

    resource_stat.stat_steal | string | The identifier used for steal stat.

    resource_stat.stat_resist_steal | string | The identifier used for the resistance to steal stat.

    resource_stat.stat_heal | string | The identifier used for heal-over-time effects.

    resource_stat.stat_heal_percent | string | The identifier used for percentage-based heal-over-time effects.

    resource_stat.menu_filename | filename | The MenuStatBar definition file to use for displaying this stat.

    resource_stat.text_base | string | The printed name of the base ("Max") stat as seen in-game.

    resource_stat.text_base_desc | string | The printed description of the base ("Max") stat as seen in-game.

    resource_stat.text_regen | string | The name of the regeneration stat as seen in-game.

    resource_stat.text_regen_desc | string | The description of the regeneration stat as seen in-game.

    resource_stat.text_steal | string | The name of the steal stat as seen in-game.

    resource_stat.text_steal_desc | string | The description of the steal stat as seen in-game.

    resource_stat.text_resist_steal | string | The name of the resistance to steal stat as seen in-game.

    resource_stat.text_resist_steal_desc | string | The description of the resistance to steal stat as seen in-game.

    resource_stat.text_combat_heal | string | The name of the stat in combat text as seen during heal-over-time.

    resource_stat.text_log_restore | string | The text in the player's log when this stat is restored via EventManager's 'restore' property.

    resource_stat.text_log_low | string | The text in the player's log when trying to use a Power that requires more than the available amount of this resource.

    resource_stat.text_tooltip_heal | string | The text in Power tooltips used for heal-over-time Effects.

    resource_stat.text_tooltip_cost | string | The text in Power tooltips that describes the casting cost of this resource.


    EventManager

    Description of events in maps/ and npcs/

    event.type | string | (IGNORED BY ENGINE) The "type" field, as used by Tiled and other mapping tools.

    event.activate | ["on_trigger", "on_interact", "on_load", "on_leave", "on_mapexit", "on_clear", "static"] | Set the state in which the event will be activated (map events only). on_trigger = the player is standing in the event area or the player interacts with the hotspot. on_interact = the player ineracts with the hotspot. on_mapexit = as the player leaves the map. on_leave = as the player steps outside of an event area they were previously inside of. on_load = as the player enters a map. on_clear = all of the enemies on a map have been defeated. static = constantly, every frame.

    event.location | rectangle | Defines the location area for the event.

    event.hotspot | ["location", rectangle] | Event uses location as hotspot or defined by rect.

    event.cooldown | duration | Duration for event cooldown in 'ms' or 's'.

    event.delay | duration | Event will execute after a specified duration.

    event.reachable_from | rectangle | If the hero is inside this rectangle, they can activate the event.

    event.tooltip | string | Tooltip for event

    event.power_path | int, int, ["hero", point] : Source X, Source Y, Destination | Path that an event power will take.

    event.power_damage | float, float : Min, Max | Range of power damage

    event.intermap | filename, int, int : Map file, X, Y | Jump to specific map at location specified.

    event.intermap_random | filename | Pick a random map from a map list file and teleport to it.

    event.intramap | int, int : X, Y | Jump to specific position within current map.

    event.mapmod | list(predefined_string, int, int, int) : Layer, X, Y, Tile ID | Modify map tiles

    event.soundfx | filename, int, int, bool : Sound file, X, Y, loop | Filename of a sound to play. Optionally, it can be played at a specific location and/or looped.

    event.loot | list(loot) | Add loot to the event.

    event.loot_count | int, int : Min, Max | Sets the minimum (and optionally, the maximum) amount of loot this event can drop. Overrides the global drop_max setting.

    event.msg | string | Adds a message to be displayed for the event.

    event.shakycam | duration | Makes the camera shake for this duration in 'ms' or 's'.

    event.requires_status | list(string) | Event requires list of statuses

    event.requires_not_status | list(string) | Event requires not list of statuses

    event.requires_level | int | Event requires hero level

    event.requires_not_level | int | Event requires not hero level

    event.requires_currency | int | Event requires atleast this much currency

    event.requires_not_currency | int | Event requires no more than this much currency

    event.requires_item | list(item_id) | Event requires specific item (not equipped). Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    event.requires_not_item | list(item_id) | Event requires not having a specific item (not equipped). Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    event.requires_class | predefined_string | Event requires this base class

    event.requires_not_class | predefined_string | Event requires not this base class

    event.set_status | list(string) | Sets specified statuses

    event.unset_status | list(string) | Unsets specified statuses

    event.remove_currency | int | Removes specified amount of currency from hero inventory

    event.remove_item | list(item_id) | Removes specified item from hero inventory. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    event.reward_xp | int | Reward hero with specified amount of experience points.

    event.reward_currency | int | Reward hero with specified amount of currency.

    event.reward_item | (list(item_id) | Reward hero with a specified item. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer. To maintain backwards compatibility, the quantity must be defined for at least the first item in the list in order to use this syntax.

    event.reward_item | item_id, int : Item, Quantity | Reward hero with y number of item x. NOTE: This syntax is maintained for backwards compatibility. It is recommended to use the above syntax instead.

    event.reward_loot | list(loot) | Reward hero with random loot.

    event.reward_loot_count | int, int : Min, Max | Sets the minimum (and optionally, the maximum) amount of loot that reward_loot can give the hero. Defaults to 1.

    event.restore | list(["hp", "mp", "hpmp", "status", "all", predefined_string]) | Restore the hero's HP, MP, and/or status. Resource stat base IDs are also valid.

    event.power | power_id | Specify power coupled with event.

    event.spawn | list(predefined_string, int, int) : Enemy category, X, Y | Spawn an enemy from this category at location

    event.stash | bool | If true, the Stash menu if opened.

    event.npc | filename | Filename of an NPC to start dialog with.

    event.music | filename | Change background music to specified file.

    event.cutscene | filename | Show specified cutscene by filename.

    event.repeat | bool | If true, the event to be triggered again.

    event.save_game | bool | If true, the game is saved when the event is triggered. The respawn position is set to where the player is standing.

    event.book | filename | Opens a book by filename.

    event.script | filename | Loads and executes an Event from a file.

    event.chance_exec | float | Percentage chance that this event will execute when triggered.

    event.respec | ["xp", "stats", "powers"], bool : Respec mode, Ignore class defaults | Resets various aspects of the character's progression. Resetting "xp" also resets "stats". Resetting "stats" also resets "powers".

    event.show_on_minimap | bool | If true, this event will be shown on the minimap if it is the appropriate type (e.g. an intermap teleport).

    event.parallax_layers | filename | Filename of a parallax layers definition to load.

    event.random_status | repeatable(["append", "clear", "roll", "set", "unset"], list(string)) : Action, Statuses (append action only) | Used to randomly pick a status from a list, and then set or unset it. Statuses are added to the list with the "append" action. The "roll" action will randomly pick from the list and set it as the current random status. The "set" and "unset" commands will function like set_status and unset_status, with the parameter being the current random status. Lastly, the "clear" action will empty the pool of random statuses. It is recommended to clear the list before you use it, as well as after you're done to prevent unintended side-effects.


    EventManager: Random Map List

    Description of maps/random/lists/

    map | filename, int, int : Map file, X, Y | Adds a map and optional spawn position to the random list of maps to teleport to.


    FogOfWar

    Description of engine/fow_mask.txt

    header.radius | int | Fog of war mask radius, also how far the player can see.

    header.bits_per_tile | int | How may bits(subdivisions) a tile is made of. In powers of two. Example: if it is set to 4 then the tile will be subdivided in 4, let's say North, South, East, West.

    header.color_dark | color | Tint color for dark tiles. Used by fog of war type 2-tint.

    header.color_fog | color | Tint color for fog tiles. Used by fog of war type 2-tint.

    header.tileset_dark | filename | Filename of a tileset definition to use for unvisited areas. Used by fog of war type 3-overlay.

    header.tileset_fog | filename | Filename of a tileset definition to use for foggy areas. Used by fog of war type 3-overlay.

    bits.bit | string, int : Name, Value | A fog of war bit definition can have any name. Better to keep it simple and short. There must be a bit definition that has the value 0. Example: If we have 4 bits per tile then we define: bit=BIT_0,0, bit=BIT_N,1, bit=BIT_W,2, bit=BIT_S,3, bit=BIT_E,4.

    tiles.tile | string, repeatable(predefined_string) : Name, Bit definitions | A fog of war tile definition can have any name. Better to keep it simple and short. There must be a tile definition that contains no bits and a tile definition that contains all bits. Example: A tile containing North and West bits will be tile=NW,BIT_N,BIT_W.

    mask.data | raw | The mask definition is a matrix (2*radius+1 by 2*radius+1) that contains fog of war tile definitions. All the margins of the matrix must be the tile definition that contains all bits.


    FontEngine: Font colors

    Description of engine/font_colors.txt

    menu_normal | color | Basic menu text color. Recommended: white.

    menu_bonus | color | Positive menu text color. Recommended: green.

    menu_penalty | color | Negative menu text color. Recommended: red.

    widget_normal | color | Basic widget text color. Recommended: white.

    widget_disabled | color | Disabled widget text color. Recommended: grey.

    combat_givedmg | color | Enemy damage text color. Recommended: white.

    combat_takedmg | color | Player damage text color. Recommended: red.

    combat_crit | color | Enemy critical damage text color. Recommended: yellow.

    combat_buff | color | Healing/buff text color. Recommended: green.

    combat_miss | color | Missed attack text color. Recommended: grey.

    requirements_not_met | color | Unmet requirements text color. Recommended: red.

    item_bonus | color | Item bonus text color. Recommended: green.

    item_penalty | color | Item penalty text color. Recommended: red.

    item_flavor | color | Item flavor text color. Recommended: grey.

    hardcore_color_name | color | Permadeath save slot player name color. Recommended: red.


    Cutscene

    Description of cutscenes in cutscenes/

    caption_margins | float, float : X margin, Y margin | Percentage-based margins for the caption text based on screen size

    caption_background | color, int : Color, Alpha | Color (RGBA) of the caption area background.

    vscroll_speed | float | The speed at which elements will scroll in 'vscroll' scenes. Defaults to 0.5.

    menu_backgrounds | bool | This cutscene will use a random fullscreen background image, like the title screen does

    music | filename | The music file that will play during this cutscene.

    scene.caption | string | A caption that will be shown.

    scene.image | filename, int : Filename, Scaling type | Filename of an image that will be shown. The scaling type is a value between 0-2, corresponding to: none, fit height, fit screen.

    scene.pause | duration | Pause before next component in 'ms' or 's'. A value of '-1' may be used to pause indefinitely.

    scene.soundfx | filename | Filename of a sound that will be played

    vscroll.text | string | A single, non-wrapping line of text.

    vscroll.image | filename | Filename of an image that will be shown.

    vscroll.separator | int | Places an invisible gap of a specified height between elements.


    GameStateLoad

    Description of menus/gameload.txt

    button_new | int, int, alignment : X, Y, Alignment | Position of the "New Game" button.

    button_load | int, int, alignment : X, Y, Alignment | Position of the "Load Game" button.

    button_delete | int, int, alignment : X, Y, Alignment | Position of the "Delete Save" button.

    button_exit | int, int, alignment : X, Y, Alignment | Position of the "Exit to Title" button.

    portrait | rectangle | Position and dimensions of the portrait image.

    gameslot | rectangle | Position and dimensions of the first game slot.

    name | label | The label for the hero's name. Position is relative to game slot position.

    level | label | The label for the hero's level. Position is relative to game slot position.

    class | label | The label for the hero's class. Position is relative to game slot position.

    map | label | The label for the hero's current location. Position is relative to game slot position.

    slot_number | label | The label for the save slot index. Position is relative to game slot position.

    loading_label | label | The label for the "Entering game world..."/"Loading saved game..." text.

    sprite | point | Position for the avatar preview image in each slot

    visible_slots | int | The maximum numbers of visible save slots.

    text_trim_boundary | int | The position of the right-side boundary where text will be shortened with an ellipsis. Position is relative to game slot position.


    GameStateNew: Layout

    Description of menus/gamenew.txt

    button_prev | int, int, alignment : X, Y, Alignment | Position of button to choose the previous preset hero.

    button_next | int, int, alignment : X, Y, Alignment | Position of button to choose the next preset hero.

    button_exit | int, int, alignment : X, Y, Alignment | Position of "Cancel" button.

    button_create | int, int, alignment : X, Y, Alignment | Position of "Create" button.

    button_permadeath | int, int, alignment : X, Y, Alignment | Position of checkbox for toggling permadeath.

    button_randomize | int, int, alignment : X, Y, Alignment | Position of the "Randomize" button.

    name_input | int, int, alignment : X, Y, Alignment | Position of the hero name textbox.

    portrait_label | label | Label for the "Choose a Portrait" text.

    name_label | label | Label for the "Choose a Name" text.

    permadeath_label | label | Label for the "Permadeath?" text.

    classlist_label | label | Label for the "Choose a Class" text.

    classlist_height | int | Number of visible rows for the class list widget.

    portrait | rectangle | Position and dimensions of the portrait image.

    class_list | int, int, alignment : X, Y, Alignment | Position of the class list.

    show_classlist | bool | Allows hiding the class list.

    show_randomize | bool | Toggles the visibility of the "Randomize" button.

    random_option | bool | Initially picks a random character option (aka portrait/name).

    random_class | bool | Initially picks a random character class.


    GameStateNew: Hero options

    Description of engine/hero_options.txt

    option | int, string, string, filename, string : Index, Base, Head, Portrait, Name | A default body, head, portrait, and name for a hero.


    GameStatePlay: Titles

    Description of engine/titles.txt

    title.title | string | The displayed title.

    title.level | int | Requires level.

    title.power | power_id | Requires power.

    title.requires_status | list(string) | Requires status.

    title.requires_not_status | list(string) | Requires not status.

    title.primary_stat | predefined_string, predefined_string : Primary stat, Lesser primary stat | Required primary stat(s). The lesser stat is optional.


    GameStateTitle

    Description of menus/gametitle.txt

    logo | filename, int, int, alignment : Image file, X, Y, Alignment | Filename and position of the main logo image.

    play_pos | int, int, alignment : X, Y, Alignment | Position of the "Play Game" button.

    config_pos | int, int, alignment : X, Y, Alignment | Position of the "Configuration" button.

    credits_pos | int, int, alignment : X, Y, Alignment | Position of the "Credits" button.

    exit_pos | int, int, alignment : X, Y, Alignment | Position of the "Exit Game" button.


    GameSwitcher: Default music

    Description of engine/default_music.txt

    music | filename | Filename of a music file to play during game states that don't already have music.


    GameSwitcher: Background images

    Description of engine/menu_backgrounds.txt

    background | repeatable(filename) | Filename of a background image to be added to the pool of random menu backgrounds


    GameSwitcher: FPS counter

    Description of menus/fps.txt

    position | int, int, alignment : X, Y, Alignment | Position of the fps counter.

    color | color | Color of the fps counter text.


    IconManager

    Description of engine/icons.txt

    icon_set | repeatable(icon_id, filename) : First ID, Image file | Defines an icon graphics file to load, as well as the index of the first icon.

    text_offset | point | A pixel offset from the top-left to place item quantity text on icons.


    InputState: Default Keybindings

    Description of engine/default_keybindings.txt. Use a bind value of '-1' to clear all bindings for an action. Type may be any of the follwing: 0 = Keyboard, 1 = Mouse, 2 = Gamepad button, 3 = Gamepad Axis. Human-readable key and gamepad mapping names may be used by prefixing the bind with "SDL:" (e.g. "SDL:space" or "SDL:leftstick"). If using the "SDL:" prefix for a gamepad axis, add ":-" to the end of the bind to get the negative direction (e.g. "SDL:leftx:-").

    default.cancel | [int, string], int : Bind, Type | Bindings for "Cancel".

    default.accept | [int, string], int : Bind, Type | Bindings for "Accept".

    default.up | [int, string], int : Bind, Type | Bindings for "Up".

    default.down | [int, string], int : Bind, Type | Bindings for "Down".

    default.left | [int, string], int : Bind, Type | Bindings for "Left".

    default.right | [int, string], int : Bind, Type | Bindings for "Right".

    default.bar1 | [int, string], int : Bind, Type | Bindings for "Bar1".

    default.bar2 | [int, string], int : Bind, Type | Bindings for "Bar2".

    default.bar3 | [int, string], int : Bind, Type | Bindings for "Bar3".

    default.bar4 | [int, string], int : Bind, Type | Bindings for "Bar4".

    default.bar5 | [int, string], int : Bind, Type | Bindings for "Bar5".

    default.bar6 | [int, string], int : Bind, Type | Bindings for "Bar6".

    default.bar7 | [int, string], int : Bind, Type | Bindings for "Bar7".

    default.bar8 | [int, string], int : Bind, Type | Bindings for "Bar8".

    default.bar9 | [int, string], int : Bind, Type | Bindings for "Bar9".

    default.bar0 | [int, string], int : Bind, Type | Bindings for "Bar0".

    default.main1 | [int, string], int : Bind, Type | Bindings for "Main1".

    default.main2 | [int, string], int : Bind, Type | Bindings for "Main2".

    default.character | [int, string], int : Bind, Type | Bindings for "Character".

    default.inventory | [int, string], int : Bind, Type | Bindings for "Inventory".

    default.powers | [int, string], int : Bind, Type | Bindings for "Powers".

    default.log | [int, string], int : Bind, Type | Bindings for "Log".

    default.equipment_swap | [int, string], int : Bind, Type | Bindings for "Next Equip Set".

    default.equipment_swap_prev | [int, string], int : Bind, Type | Bindings for "Previous Equip Set".

    default.minimap_mode | [int, string], int : Bind, Type | Bindings for "Mini-map Mode".

    default.loot_tooltip_mode | [int, string], int : Bind, Type | Bindings for "Loot Tooltip Mode".

    default.actionbar | [int, string], int : Bind, Type | Bindings for "Action Bar".

    default.menu_page_next | [int, string], int : Bind, Type | Bindings for "Menu: Next Page".

    default.menu_page_prev | [int, string], int : Bind, Type | Bindings for "Menu: Previous Page".

    default.menu_activate | [int, string], int : Bind, Type | Bindings for "Menu: Activate".

    default.pause | [int, string], int : Bind, Type | Bindings for "Pause".

    default.aim_up | [int, string], int : Bind, Type | Bindings for "Aim Up".

    default.aim_down | [int, string], int : Bind, Type | Bindings for "Aim Down".

    default.aim_left | [int, string], int : Bind, Type | Bindings for "Aim Left".

    default.aim_right | [int, string], int : Bind, Type | Bindings for "Aim Right".

    default.developer_menu | [int, string], int : Bind, Type | Bindings for "Developer Menu".


    ItemManager: Items

    Description about the class and it usage, items/items.txt...

    id | item_id | An uniq id of the item used as reference from other classes.

    name | string | Item name displayed on long and short tooltips.

    flavor | string | A description of the item.

    level | int | The item's level. Has no gameplay impact. (Deprecated?)

    icon | icon_id | An id for the icon to display for this item.

    book | filename | A book file to open when this item is activated.

    book_is_readable | bool | If true, "read" is displayed in the tooltip instead of "use". Defaults to true.

    quality | predefined_string | Item quality matching an id in items/qualities.txt

    item_type | predefined_string | Equipment slot matching an id in items/types.txt

    equip_flags | list(predefined_string) | A comma separated list of flags to set when this item is equipped. See engine/equip_flags.txt.

    dmg | predefined_string, float, float : Damage type, Min, Max | Defines the item's base damage type and range. Max may be ommitted and will default to Min.

    abs | float, float : Min, Max | Defines the item absorb value, if only min is specified the absorb value is fixed.

    requires_level | int | The hero's level must match or exceed this value in order to equip this item.

    requires_stat | repeatable(predefined_string, int) : Primary stat name, Value | Make item require specific stat level ex. requires_stat=physical,6 will require hero to have level 6 in physical stats

    requires_class | predefined_string | The hero's base class (engine/classes.txt) must match for this item to be equipped.

    bonus | repeatable(stat_id, float) : Stat ID, Value | Adds a bonus to the item by stat ID, example: bonus=hp,50

    bonus_power_level | repeatable(power_id, int) : Base power, Bonus levels | Grants bonus levels to a given base power.

    soundfx | filename | Sound effect filename to play for the specific item.

    gfx | filename | Filename of an animation set to display when the item is equipped.

    loot_animation | repeatable(filename, int, int) : Loot image, Min quantity, Max quantity | Specifies the loot animation file for the item. The max quantity, or both quantity values, may be omitted.

    power | power_id | Adds a specific power to the item which makes it usable as a power and can be placed in action bar.

    replace_power | repeatable(int, int) : Old power, New power | Replaces the old power id with the new power id in the action bar when equipped.

    power_desc | string | A string describing the additional power.

    price | int | The amount of currency the item costs, if set to 0 the item cannot be sold.

    price_per_level | int | Additional price for each player level above 1

    price_sell | int | The amount of currency the item is sold for, if set to 0 the sell prices is prices*vendor_ratio.

    max_quantity | int | Max item count per stack.

    pickup_status | string | Set a campaign status when item is picked up, this is used for quest items.

    stepfx | predefined_string | Sound effect when walking, this applies only to armors.

    disable_slots | list(predefined_string) | A comma separated list of equip slot types to disable when this item is equipped.

    quest_item | bool | If true, this item is a quest item and can not be dropped or sold. The item also can't be stashed, unless the no_stash property is set to something other than "all".

    no_stash | ["ignore", "private", "shared", "all"] | If not set to 'ignore', this item will not be able to be put in the corresponding stash.

    script | filename | Loads and executes a script file when the item is activated from the player's inventory.


    ItemManager: Types

    Definition of a item types, items/types.txt...

    type.id | string | Item type identifier.

    type.name | string | Item type name.


    ItemManager: Qualities

    Definition of a item qualities, items/types.txt...

    quality.id | string | Item quality identifier.

    quality.name | string | Item quality name.

    quality.color | color | Item quality color.

    quality.overlay_icon | icon_id | The icon to be used as an overlay.


    ItemManager: Sets

    Definition of a item sets, items/sets.txt...

    id | int | A uniq id for the item set.

    name | string | Name of the item set.

    items | list(item_id) | List of item id's that is part of the set.

    color | color | A specific of color for the set.

    bonus | repeatable(int, stat_id, float) : Required set item count, Stat ID, Value | Bonus to append to items in the set.

    bonus_power_level | repeatable(int, power_id, int) : Required set item count, Base power, Bonus levels | Grants bonus levels to a given base power.


    LootManger

    Description of loot tables in loot/

    loot | loot | Compact form of defining a loot table entry.

    status_loot | string, loot : Required status, Loot definition | Compact form of defining a loot table entry with a required campaign status.

    loot.id | [item_id, "currency"] | The ID of the loot item. "currency" will use the item ID defined as currency_id in engine/misc.txt.

    loot.chance | [float, "fixed"] | The chance that the item will drop. "fixed" will drop the item no matter what before the random items are picked. This is different than setting a chance of 100, in which the item could be replaced with another random item.

    loot.quantity | int, int : Min quantity, Max quantity (optional) | The quantity of item in the dropped loot stack.

    loot.requires_status | string | A single campaign status that is required for the item to be able to drop.


    Map

    Description of maps/

    title | string | Title of map

    width | int | Width of map

    height | int | Height of map

    tileset | filename | Filename of a tileset definition to use for map

    music | filename | Filename of background music to use for map

    hero_pos | point | The player will spawn in this location if no point was previously given.

    parallax_layers | filename | Filename of a parallax layers definition.

    background_color | color, int : Color, alpha | Background color for the map.

    fogofwar | int | Set the fog of war type. 0-disabled, 1-minimap, 2-tint, 3-overlay. Overrides engine settings.

    save_fogofwar | bool | If true, the fog of war layer keeps track of the progress. Overrides engine settings.

    tilewidth | int | Inherited from Tiled map file. Unused by engine.

    tileheight | int | Inherited from Tiled map file. Unused by engine.

    layer.type | string | Map layer type.

    layer.format | string | Format for map layer, must be 'dec'

    layer.data | raw | Raw map layer data

    enemygroup.type | string | (IGNORED BY ENGINE) The "type" field, as used by Tiled and other mapping tools.

    enemygroup.category | predefined_string | The category of enemies that will spawn in this group.

    enemygroup.level | int, int : Min, Max | Defines the level range of enemies in group. If only one number is given, it's the exact level.

    enemygroup.location | rectangle | Location area for enemygroup

    enemygroup.number | int, int : Min, Max | Defines the range of enemies in group. If only one number is given, it's the exact amount.

    enemygroup.chance | float | Initial percentage chance that this enemy group will be able to spawn enemies.

    enemygroup.direction | direction | Direction that enemies will initially face.

    enemygroup.waypoints | list(point) | Enemy waypoints; single enemy only; negates wander_radius

    enemygroup.wander_radius | int | The radius (in tiles) that an enemy will wander around randomly; negates waypoints

    enemygroup.requires_status | list(string) | Statuses required to be set for enemy group to load

    enemygroup.requires_not_status | list(string) | Statuses required to be unset for enemy group to load

    enemygroup.requires_level | int | Player level must be equal or greater to load enemy group

    enemygroup.requires_not_level | int | Player level must be lesser to load enemy group

    enemygroup.requires_currency | int | Player currency must be equal or greater to load enemy group

    enemygroup.requires_not_currency | int | Player currency must be lesser to load enemy group

    enemygroup.requires_item | list(item_id) | Item required to exist in player inventory to load enemy group. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    enemygroup.requires_not_item | list(item_id) | Item required to not exist in player inventory to load enemy group. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    enemygroup.requires_class | predefined_string | Player base class required to load enemy group

    enemygroup.requires_not_class | predefined_string | Player base class not required to load enemy group

    enemygroup.invincible_requires_status | list(string) | Enemies in this group are invincible to hero attacks when these statuses are set.

    enemygroup.invincible_requires_not_status | list(string) | Enemies in this group are invincible to hero attacks when these statuses are not set.

    enemygroup.spawn_level | ["default", "fixed", "level", "stat"], int, int, predefined_string : Mode, Enemy Level, Ratio, Primary stat | The level of spawned creatures. The need for the last three parameters depends on the mode being used. The "default" mode will just use the entity's normal level and doesn't require any additional parameters. The "fixed" mode only requires the enemy level as a parameter. The "stat" and "level" modes also require the ratio as a parameter. The ratio adjusts the scaling of the entity level. For example, spawn_level=stat,1,2,physical will set the spawned entity level to 1/2 the player's Physical stat. Only the "stat" mode requires the last parameter, which is simply the ID of the primary stat that should be used for scaling.

    npc.type | string | (IGNORED BY ENGINE) The "type" field, as used by Tiled and other mapping tools.

    npc.filename | string | Filename of an NPC definition.

    npc.location | point | Location of NPC

    npc.requires_status | list(string) | Statuses required to be set for NPC load

    npc.requires_not_status | list(string) | Statuses required to be unset for NPC load

    npc.requires_level | int | Player level must be equal or greater to load NPC

    npc.requires_not_level | int | Player level must be lesser to load NPC

    npc.requires_currency | int | Player currency must be equal or greater to load NPC

    npc.requires_not_currency | int | Player currency must be lesser to load NPC

    npc.requires_item | list(item_id) | Item required to exist in player inventory to load NPC. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    npc.requires_not_item | list(item_id) | Item required to not exist in player inventory to load NPC. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    npc.requires_class | predefined_string | Player base class required to load NPC

    npc.requires_not_class | predefined_string | Player base class not required to load NPC

    npc.direction | direction | Direction that NPC will initially face.


    MapParallax

    Description of maps/parallax/

    layer.image | filename | Image file to use as a scrolling background.

    layer.speed | float | Speed at which the background will move relative to the camera.

    layer.fixed_speed | float, float : X speed, Y speed | Speed at which the background will move independent of the camera movement.

    layer.map_layer | string | The tile map layer that this parallax layer will be rendered on top of.


    MenuActionBar

    Description of menus/actionbar.txt

    slot | repeatable(int, int, int, bool) : Index, X, Y, Locked | Index (max 10) and position for power slot. If a slot is locked, its Power can't be changed by the player.

    slot_M1 | point, bool : Position, Locked | Position for the primary action slot. If the slot is locked, its Power can't be changed by the player.

    slot_M2 | point, bool : Position Locked | Position for the secondary action slot. If the slot is locked, its Power can't be changed by the player.

    char_menu | point | Position for the Character menu button.

    inv_menu | point | Position for the Inventory menu button.

    powers_menu | point | Position for the Powers menu button.

    log_menu | point | Position for the Log menu button.

    tooltip_length | ["short", "long_menu", "long_all"] | The length of power descriptions in tooltips. 'short' will display only the power name. 'long_menu' (the default setting) will display full tooltips, but only for powers that are in the Powers menu. 'long_all' will display full tooltips for all powers.


    MenuActiveEffects

    Description of menus/activeeffects.txt

    vertical | bool | True is vertical orientation; False is horizontal orientation.


    MenuBook

    Description of books in books/

    close | point | Position of the close button.

    image.image_pos | point | Position of the image.

    image.image | filename | Filename of the image.

    image.image_icon | icon_id | Use an icon as the image instead of a file.

    image.requires_status | list(string) | Image requires these campaign statuses in order to be visible.

    image.requires_not_status | list(string) | Image must not have any of these campaign statuses in order to be visible.

    text.text_pos | int, int, int, ["left", "center", "right"] : X, Y, Width, Text justify | Position of the text.

    text.text_font | color, string : Font color, Font style | Font color and style.

    text.text_shadow | bool | If true, the text will have a black shadow like the text labels in various menus.

    text.text | string | The text to be displayed.

    text.requires_status | list(string) | Text requires these campaign statuses in order to be visible.

    text.requires_not_status | list(string) | Text must not have any of these campaign statuses in order to be visible.

    button.button_pos | point | Position of the button.

    button.button_image | filename | Image file to use for this button. Default is the normal menu button.

    button.text | string | Optional text label for the button.

    button.${EVENT_COMPONENT} | Event components to execute when the button is clicked. See the definitions in EventManager for possible attributes. |

    event_open.${EVENT_COMPONENT} | Event components to execute when the book is opened. See the definitions in EventManager for possible attributes. |

    event_close.${EVENT_COMPONENT} | Event components to execute when the book is closed. See the definitions in EventManager for possible attributes. |


    MenuCharacter

    Description of menus/character.txt

    close | point | Position of the close button.

    label_title | label | Position of the "Character" text.

    upgrade_primary | predefined_string, point : Primary stat name, Button position | Position of the button used to add a stat point to this primary stat.

    statlist | point | Position of the scrollbox containing non-primary stats.

    statlist_rows | int | The height of the statlist in rows.

    statlist_scrollbar_offset | int | Right margin in pixels for the statlist's scrollbar.

    label_name | label | Position of the "Name" text.

    label_level | label | Position of the "Level" text.

    label_primary | predefined_string, label : Primary stat name, Text positioning | Position of the text label for this primary stat.

    name | rectangle | Position of the player's name and dimensions of the tooltip hotspot.

    level | rectangle | Position of the player's level and dimensions of the tooltip hotspot.

    primary | predefined_string, rectangle : Primary stat name, Hotspot position | Position of this primary stat value display and dimensions of its tooltip hotspot.

    unspent | label | Position of the label showing the number of unspent stat points.

    show_resists | bool | Hide the elemental "Resistance" stats in the statlist if set to false.

    show_stat | stat_id, bool : Stat ID, Visible | Hide the matching stat ID in the statlist if set to false.

    name_max_width | int | The maxiumum width, in pixels, that the character name can occupy until it is abbreviated.


    MenuConfig

    Description of menus/config.txt

    button_ok | int, int, alignment : X, Y, Alignment | Position of the "OK" button. Not used in the pause menu.

    button_defaults | int, int, alignment : X, Y, Alignment | Position of the "Defaults" button. Not used in the pause menu.

    button_cancel | int, int, alignment : X, Y, Alignment | Position of the "Cancel" button. Not used in the pause menu.

    listbox_scrollbar_offset | int | Horizontal offset from the right of listboxes (mods, languages, etc) to place the scrollbar.

    frame_offset | point | Offset for all the widgets contained under each tab.

    tab_offset | point | Offset for the row of tabs.

    activemods | int, int, int, int : Label X, Label Y, Widget X, Widget Y | Position of the "Active Mods" list box relative to the frame.

    activemods_height | int | Number of visible rows for the "Active Mods" list box.

    inactivemods | int, int, int, int : Label X, Label Y, Widget X, Widget Y | Position of the "Available Mods" list box relative to the frame.

    inactivemods_height | int | Number of visible rows for the "Available Mods" list box.

    activemods_shiftup | point | Position of the button to shift mods up in "Active Mods" relative to the frame.

    activemods_shiftdown | point | Position of the button to shift mods down in "Active Mods" relative to the frame.

    activemods_deactivate | point | Position of the "Disable" button relative to the frame.

    inactivemods_activate | point | Position of the "Enable" button relative to the frame.

    scrollpane | rectangle | Position of the keybinding scrollbox relative to the frame.

    scrollpane_padding | int, int : Horizontal padding, Vertical padding | Pixel padding for each item listed in a tab's scroll box.

    scrollpane_separator_color | color | Color of the separator line in between scroll box items.

    keybinds_bg_color | color, int : Color, Alpha | Background color and alpha for the keybindings scrollbox.


    MenuConfirm

    Description of menus/confirm.txt

    close | point | Position of the close button.

    label_title | label | Position of the title text.

    action_list | point | Position of the action selector widget.


    Menu

    Description of menus in menus/

    pos | rectangle | Menu position and dimensions

    align | alignment | Position relative to screen edges

    soundfx_open | filename | Filename of a sound to play when opening this menu.

    soundfx_close | filename | Filename of a sound to play when closing this menu.

    background | filename | Filename of the background image for this menu.


    MenuDevConsole

    Description of menus/devconsole.txt

    close | point | Position of the close button.

    label_title | label | Position of the "Developer Console" label.

    confirm | point | Position of the "Execute" button.

    input | point | Position of the command entry widget.

    history | rectangle | Position and dimensions of the command history.


    MenuEnemy

    Description of menus/enemy.txt

    bar_pos | rectangle | Position and dimensions of the health bar.

    text_pos | label | Position of the text displaying the enemy's name and level.

    bar_fill_offset | point | Offset of the bar's fill graphics relative to the bar_pos X/Y.

    bar_fill_size | int, int : Width, Height | Size of the bar's fill graphics. If not defined, the width/height of bar_pos is used.

    bar_gfx | filename | Filename of the image to use for the "fill" of the bar.


    MenuGameOver

    Description of menus/game_over.txt

    label_title | label | Position of the "Game Over" text.

    button_continue | point | Position of the "Continue" button.

    button_exit | point | Position of the "Exit" button.


    MenuHUDLog

    Description of menus/hudlog.txt

    enable_overlay | bool | If true, shows an overlay of the last message on top of other menus.

    start_at_bottom | bool | If true, messages start at the bottom and get pushed up. If false, messages start at the top and get pushed down.

    overlay_at_bottom | bool | If true, the overlay message will be at the bottom of the HUD log area. If false, it will be at the top.


    MenuInventory

    Description of menus/inventory.txt

    close | point | Position of the close button.

    set_button | int, int, int, filename : ID, Widget X, Widget Y, Image file | Set number, position and image filename for an equipment swap set button.

    set_previous | int, int, filename : Widget X, Widget Y, Image file | Position and image filename for an equipment swap set previous button.

    set_next | int, int, filename : Widget X, Widget Y, Image file | Position and image filename for an equipment swap set next button.

    label_equipment_set | label | Label showing the active equipment set.

    equipment_slot | repeatable(int, int, string, int) : X, Y, Slot Type, Equipment set | Position, item type and equipment set number of an equipment slot. Equipment set number is "0" for shared items."

    carried_area | point | Position of the first normal inventory slot.

    carried_cols | int | The number of columns for the normal inventory.

    carried_rows | int | The number of rows for the normal inventory.

    label_title | label | Position of the "Inventory" label.

    currency | label | Position of the label that displays the total currency being carried.

    help | rectangle | A mouse-over area that displays some help text for inventory shortcuts.


    MenuLog

    Description of menus/log.txt

    label_title | label | Position of the "Log" text.

    close | point | Position of the close button.

    tab_area | rectangle | The position of the row of tabs, followed by the dimensions of the log text area.


    MenuMiniMap

    Description of menus/minimap.txt

    map_pos | rectangle | Position and dimensions of the map.

    text_pos | label | Position of the text label with the map name.

    color_wall | color, int : Color, Alpha | Color used for walls.

    color_obst | color, int : Color, Alpha | Color used for small obstacles and pits.

    color_hero | color, int : Color, Alpha | Color used for the player character.

    color_enemy | color, int : Color, Alpha | Color used for enemies engaged in combat.

    color_ally | color, int : Color, Alpha | Color used for allies.

    color_npc | color, int : Color, Alpha | Color used for NPCs.

    color_teleport | color, int : Color, Alpha | Color used for intermap teleports.

    button_config | point | Position of the 'Configuration' button. The button will be hidden if not defined.


    MenuMovementType

    Description of menus/movement_type.txt

    label_title | label | Position of the "Select a Movement Type" text.

    label_config_hint | label | Position of the "Can be changed later in the Configuration menu" text.

    button_keyboard | point | Position of the "Keyboard" button.

    button_mouse | point | Position of the "Mouse" button.

    button_joystick | point | Position of the "Joystick" button.

    icon_keyboard | point | Position of the keyboard icon.

    icon_mouse | point | Position of the mouse icon.

    icon_joystick | point | Position of the joystick icon.


    MenuNumPicker

    Description of menus/num_picker.txt

    label_title | label | Position of the "Enter amount:" text.

    confirm | point | Position of the "OK" button.

    increase | point | Position of the button used to increase the value.

    decrease | point | Position of the button used to decrease the value.

    close | point | Position of the button used to close the number picker window.

    input | point | Position of the text input box.


    MenuPowers: Menu layout

    Description of menus/powers.txt

    label_title | label | Position of the "Powers" text.

    unspent_points | label | Position of the text that displays the amount of unused power points.

    close | point | Position of the close button.

    tab_area | rectangle | Position and dimensions of the tree pages.


    MenuPowers: Power tree layout

    Description of powers/trees/

    background | filename | Filename of the default background image

    tab.title | string | The name of this power tree tab

    tab.background | filename | Filename of the background image for this tab's power tree

    power.id | int | A power id from powers/powers.txt for this slot.

    power.tab | int | Tab index to place this power on, starting from 0.

    power.position | point | Position of this power icon; relative to MenuPowers "pos".

    power.requires_point | bool | Power requires a power point to unlock.

    power.requires_primary | predefined_string, int : Primary stat name, Required value | Power requires this primary stat to be at least the specificed value.

    power.requires_level | int | Power requires at least this level for the hero.

    power.requires_power | power_id | Power requires another power id.

    power.requires_status | repeatable(string) | Power requires this campaign status.

    power.requires_not_status | repeatable(string) | Power requires not having this campaign status.

    power.visible_requires_status | repeatable(string) | (Deprecated as of v1.11.75) Hide the power if we don't have this campaign status.

    power.visible_requires_not_status | repeatable(string) | (Deprecated as of v1.11.75) Hide the power if we have this campaign status.

    power.upgrades | list(power_id) | A list of upgrade power ids that this power slot can upgrade to. Each of these powers should have a matching upgrade section.

    power.visible | bool | Controls whether or not a power is visible or hidden regardless of unlocked state. Defaults to true.

    power.visible_check_locked | bool | When set to true, the power will be hidden if it is locked. Defaults to false.

    power.visible_check_status | bool | When set to true, the power will be hidden if its status requirements are not met. Defaults to false.

    upgrade.id | int | A power id from powers/powers.txt for this upgrade.

    upgrade.requires_primary | predefined_string, int : Primary stat name, Required value | Upgrade requires this primary stat to be at least the specificed value.

    upgrade.requires_point | bool | Upgrade requires a power point to unlock.

    upgrade.requires_level | int | Upgrade requires at least this level for the hero.

    upgrade.requires_power | int | Upgrade requires another power id.

    upgrade.requires_status | repeatable(string) | Upgrade requires this campaign status.

    upgrade.requires_not_status | repeatable(string) | Upgrade requires not having this campaign status.

    upgrade.visible_requires_status | repeatable(string) | (Deprecated as of v1.11.75) Hide the upgrade if we don't have this campaign status.

    upgrade.visible_requires_not_status | repeatable(string) | (Deprecated as of v1.11.75) Hide the upgrade if we have this campaign status.

    upgrade.visible | bool | Controls whether or not a power is visible or hidden regardless of unlocked state. Defaults to true.

    upgrade.visible_check_locked | bool | When set to true, the power will be hidden if it is locked. Defaults to false.

    upgrade.visible_check_status | bool | When set to true, the power will be hidden if its status requirements are not met. Defaults to false.


    MenuStash

    Description of menus/stash.txt

    close | point | Position of the close button.

    slots_area | point | Position of the top-left slot.

    stash_cols | int | The number of columns for the grid of slots.

    stash_rows | int | The number of rows for the grid of slots.

    label_title | label | Position of the "Stash" label.

    currency | label | Position of the label displaying the amount of currency stored in the stash.

    tab.name | ["Private", "Shared", string] | The displayed name of this tab. It is also used to determine the filename of the stash file that the engine will create. 'Private' and 'Shared' will use their legacy filenames for compatibility.

    tab.is_private | bool | If true, this stash will not be shared across other saves.


    MenuStatBar

    Description of menus/hp.txt, menus/mp.txt, menus/xp.txt

    bar_pos | rectangle | Position and dimensions of the bar graphics.

    text_pos | label | Position of the text displaying the current value of the relevant stat.

    orientation | bool | True is vertical orientation; false is horizontal.

    bar_gfx | filename | Filename of the image to use for the "fill" of the bar.

    bar_gfx_background | filename | Filename of the image to use for the base of the bar.

    hide_timeout | duration | Hide HP and MP bar if full mana or health, after given amount of seconds; Hide XP bar if no changes in XP points for given amount of seconds. 0 disable hiding.

    bar_fill_offset | point | Offset of the bar's fill graphics relative to the bar_pos X/Y.

    bar_fill_size | int, int : Width, Height | Size of the bar's fill graphics. If not defined, the width/height of bar_pos is used.

    enabled | bool | Determines if the bar will be rendered. Disable the bar completely by setting this to false.


    MenuTalker

    Description of menus/talker.txt

    close | point | Position of the close button.

    advance | point | Position of the button to advance dialog.

    dialogbox | rectangle | Position and dimensions of the text box graphics.

    dialogtext | rectangle | Rectangle where the dialog text is placed.

    text_offset | point | Margins for the left/right and top/bottom of the dialog text.

    portrait_he | rectangle | Position and dimensions of the NPC portrait graphics.

    portrait_you | rectangle | Position and dimensions of the player's portrait graphics.

    font_who | predefined_string | Font style to use for the name of the currently talking person.

    font_dialog | predefined_string | Font style to use for the dialog text.

    topic_color_normal | color | The normal color for topic text.

    topic_color_hover | color | The color for topic text when highlighted.

    topic_color_normal | color | The color for topic text when clicked.

    trade_color_normal | color | The normal color for the "Trade" text.

    trade_color_hover | color | The color for the "Trade" text when highlighted.

    trade_color_normal | color | The color for the "Trade" text when clicked.


    MenuVendor

    Description of menus/vendor.txt

    close | point | Position of the close button.

    slots_area | point | Position of the top-left slot.

    vendor_cols | int | The number of columns in the grid of slots.

    vendor_rows | int | The number of rows in the grid of slots.

    label_title | label | The position of the text that displays the NPC's name.


    ModManager

    Description of mod settings.txt

    description | string | Some text describing the mod.

    description_locale | string, string : Language, Translated description | A translated description for a language (specified by 2-letter code).

    version | version | The version number of this mod.

    requires | list(string) | A comma-separated list of the mods that are required in order to use this mod. The dependency version requirements can also be specified and separated by colons (e.g. fantasycore:0.1:2.0).

    game | string | The game which this mod belongs to (e.g. flare-game).

    engine_version_min | version | The minimum engine version required to use this mod.

    engine_version_max | version | The maximum engine version required to use this mod.


    NPC

    Description of NPCs in npcs/

    dialog.id | string | A unique identifer used to reference this dialog.

    dialog.him | repeatable(string) | A line of dialog from the NPC.

    dialog.her | repeatable(string) | A line of dialog from the NPC.

    dialog.you | repeatable(string) | A line of dialog from the player.

    dialog.voice | repeatable(string) | Filename of a voice sound file to play.

    dialog.topic | string | The name of this dialog topic. Displayed when picking a dialog tree.

    dialog.group | string | Dialog group.

    dialog.allow_movement | bool | Restrict the player's mvoement during dialog.

    dialog.portrait_him | repeatable(filename) | Filename of a portrait to display for the NPC during this dialog.

    dialog.portrait_her | repeatable(filename) | Filename of a portrait to display for the NPC during this dialog.

    dialog.portrait_you | repeatable(filename) | Filename of a portrait to display for the player during this dialog.

    dialog.take_a_party | bool | Start/stop taking a party with player.

    dialog.response | repeatable(string) | A dialog ID to present as a selectable response. This key must precede the dialog text line.

    dialog.response_only | bool | If true, this dialog topic will only appear when explicitly referenced with the "response" key.

    npc.name | string | NPC's name.

    npc.direction | direction | The direction to use for this NPC's stance animation.

    npc.show_on_minimap | bool | If true, this NPC will be shown on the minimap. The default is true.

    npc.talker | bool | Allows this NPC to be talked to.

    npc.portrait | filename | Filename of the default portrait image.

    npc.vendor | bool | Allows this NPC to buy/sell items.

    npc.vendor_requires_status | list(string) | The player must have these statuses in order to use this NPC as a vendor.

    npc.vendor_requires_not_status | list(string) | The player must not have these statuses in order to use this NPC as a vendor.

    npc.constant_stock | repeatable(list(item_id)) | A list of items this vendor has for sale. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    npc.status_stock | repeatable(string, list(item_id)) : Required status, Item(s) | A list of items this vendor will have for sale if the required status is met. Quantity can be specified by appending ":Q" to the item_id, where Q is an integer.

    npc.random_stock | list(loot) | Use a loot table to add random items to the stock; either a filename or an inline definition.

    npc.random_stock_count | int, int : Min, Max | Sets the minimum (and optionally, the maximum) amount of random items this npc can have.

    npc.vendor_ratio_buy | float | NPC-specific version of vendor_ratio_buy from engine/loot.txt. Uses the global setting when set to 0.

    npc.vendor_ratio_sell | float | NPC-specific version of vendor_ratio_sell from engine/loot.txt. Uses the global setting when set to 0.

    npc.vendor_ratio_sell_old | float | NPC-specific version of vendor_ratio_sell_old from engine/loot.txt. Uses the global setting when set to 0.

    npc.vox_intro | repeatable(filename) | Filename of a sound file to play when initially interacting with the NPC.


    PowerManager: Effects

    Description of powers/effects.txt

    effect.id | string | Unique identifier for the effect definition.

    effect.type | string | Defines the type of effect

    effect.name | string | A displayed name that is shown when hovering the mouse over the effect icon.

    effect.icon | icon_id | The icon to visually represent the effect in the status area

    effect.animation | filename | The filename of effect animation.

    effect.can_stack | bool | Allows multiple instances of this effect

    effect.max_stacks | int | Maximum allowed instances of this effect, -1 for no limits

    effect.group_stack | bool | For effects that can stack, setting this to true will combine those effects into a single status icon.

    effect.render_above | bool | Effect is rendered above

    effect.color_mod | color | Changes the color of the afflicted entity.

    effect.alpha_mod | int | Changes the alpha of the afflicted entity.

    effect.attack_speed_anim | string | If the type of Effect is attack_speed, this defines the attack animation that will have its speed changed.


    PowerManager: Powers

    Description of powers/powers.txt

    power.id | power_id | Uniq identifier for the power definition.

    power.type | ["fixed", "missile", "repeater", "spawn", "transform", "block"] | Defines the type of power definiton

    power.name | string | The name of the power

    power.description | string | Description of the power

    power.icon | icon_id | The icon to visually represent the power eg. in skill tree or action bar.

    power.new_state | predefined_string | When power is used, hero or enemy will change to this state. Must be one of the states ["instant", user defined]

    power.state_duration | duration | Sets the length of time the caster is in their state animation. A time longer than the animation length will cause the animation to pause on the last frame. Times shorter than the state animation length will have no effect.

    power.prevent_interrupt | bool | Prevents the caster from being interrupted by a hit when casting this power.

    power.face | bool | Power will make hero or enemy to face the target location.

    power.source_type | ["hero", "neutral", "enemy"] | Determines which entities the power can effect.

    power.beacon | bool | True if enemy is calling its allies.

    power.count | int | The count of hazards/effect or spawns to be created by this power.

    power.passive | bool | If power is unlocked when the hero or enemy spawns it will be automatically activated.

    power.passive_trigger | ["on_block", "on_hit", "on_halfdeath", "on_joincombat", "on_death"] | This will only activate a passive power under a certain condition.

    power.meta_power | bool | If true, this power can not be used on it's own. Instead, it should be replaced via an item with a replace_power entry.

    power.no_actionbar | bool | If true, this power is prevented from being placed on the actionbar.

    power.requires_flags | list(predefined_string) | A comma separated list of equip flags that are required to use this power. See engine/equip_flags.txt

    power.requires_mp | float | Require an amount of MP to use the power. The amount will be consumed on usage.

    power.requires_hp | float | Require an amount of HP to use the power. The amount will be consumed on usage.

    power.requires_resource_stat | repeatable(predefined_string, float) : Resource stat ID, Required amount | Requires an amount of a given resource to use the power. The amount will be consumed on usage.

    power.sacrifice | bool | If the power has requires_hp, allow it to kill the caster.

    power.requires_los | bool | Requires a line-of-sight to target.

    power.requires_empty_target | bool | The power can only be cast when target tile is empty.

    power.requires_item | repeatable(item_id, int) : Item, Quantity | Requires a specific item of a specific quantity in inventory. If quantity > 0, then the item will be removed.

    power.requires_equipped_item | repeatable(item_id, int) : Item, Quantity | Requires a specific item of a specific quantity to be equipped on hero. If quantity > 0, then the item will be removed.

    power.requires_targeting | bool | Power is only used when targeting using click-to-target.

    power.requires_spawns | int | The caster must have at least this many summoned creatures to use this power.

    power.cooldown | duration | Specify the duration for cooldown of the power in 'ms' or 's'.

    power.requires_hpmp_state | ["all", "any"], ["percent", "not_percent", "ignore"], float , ["percent", "not_percent", "ignore"], float : Mode, HP state, HP Percentage value, MP state, MP Percentage value | Power can only be used when HP/MP matches the specified state. In 'all' mode, both HP and MP must meet the requirements, where as only one must in 'any' mode. To check a single stat, use 'all' mode and set the 'ignore' state for the other stat.

    power.requires_resource_stat_state | predefined_string, ["percent", "not_percent", "ignore"], float : Resource stat ID, State, Percentage value | Power can only be used when the resource stat matches the specified state. See 'requires_resource_stat_state_mode' for combining multiple resource states and, optionally, HP/MP state.

    power.requires_resource_stat_state_mode | ["all", "any", "any_hpmp"] | Determines how required resource state is handled for multiple resources. "all" means that HP/MP state AND all resource stat states must pass. "any" means that HP/MP state must pass AND at least one resource stat state must pass. "any_hpmp" means that HP/MP state OR at least one resource stat state is required to pass.

    power.animation | filename | The filename of the power animation.

    power.soundfx | filename | Filename of a sound effect to play when the power is used.

    power.soundfx_hit | filename | Filename of a sound effect to play when the power's hazard hits a valid target.

    power.directional | bool | The animation sprite sheet contains 8 directions, one per row.

    power.visual_random | int | The animation sprite sheet contains rows of random options

    power.visual_option | int | The animation sprite sheet containers rows of similar effects, use a specific option. If using visual_random, this serves as an offset for the lowest random index.

    power.aim_assist | bool | If true, power targeting will be offset vertically by the number of pixels set with "aim_assist" in engine/misc.txt.

    power.speed | float | The speed of missile hazard, the unit is defined as map units per frame.

    power.lifespan | duration | How long the hazard/animation lasts in 'ms' or 's'.

    power.floor | bool | The hazard is drawn between the background and the object layer.

    power.complete_animation | bool | For hazards; Play the entire animation, even if the hazard has hit a target.

    power.charge_speed | float | Moves the caster at this speed in the direction they are facing until the state animation is finished.

    power.attack_speed | float | Changes attack animation speed for this Power. A value of 100 is 100% speed (aka normal speed).

    power.use_hazard | bool | Power uses hazard.

    power.no_attack | bool | Hazard won't affect other entities.

    power.no_aggro | bool | If true, the Hazard won't put its target in a combat state.

    power.radius | float | Radius in map units

    power.base_damage | predefined_string : Damage type ID | Determines which damage stat will be used to calculate damage.

    power.starting_pos | ["source", "target", "melee"] | Start position for hazard

    power.relative_pos | bool | Hazard will move relative to the caster's position.

    power.multitarget | bool | Allows a hazard power to hit more than one entity.

    power.multihit | bool | Allows a hazard power to hit the same entity more than once.

    power.expire_with_caster | bool | If true, hazard will disappear when the caster dies.

    power.ignore_zero_damage | bool | If true, hazard can still hit the player when damage is 0, triggering post_power and post_effects.

    power.lock_target_to_direction | bool | If true, the target is "snapped" to one of the 8 directions.

    power.movement_type | ["ground", "flying", "intangible"] | For moving hazards (missile/repeater), this defines which parts of the map it can collide with. The default is "flying".

    power.trait_armor_penetration | bool | Ignores the target's Absorbtion stat

    power.trait_avoidance_ignore | bool | Ignores the target's Avoidance stat

    power.trait_crits_impaired | int | Increases critical hit percentage for slowed/immobile targets

    power.trait_elemental | predefined_string | Damage done is elemental. See engine/elements.txt

    power.target_range | float | The distance from the caster that the power can be activated

    power.hp_steal | float | Percentage of damage to steal into HP

    power.mp_steal | float | Percentage of damage to steal into MP

    power.resource_steal | repeatable(predefined_string, float) : Resource stat ID, Steal amount | Percentage of damage to steal into the specified resource

    power.missile_angle | float | Angle of missile

    power.angle_variance | float | Percentage of variance added to missile angle

    power.speed_variance | float | Percentage of variance added to missile speed

    power.delay | duration | Delay between repeats in 'ms' or 's'.

    power.transform_duration | duration | Duration for transform in 'ms' or 's'.

    power.manual_untransform | bool | Force manual untranform

    power.keep_equipment | bool | Keep equipment while transformed

    power.untransform_on_hit | bool | Force untransform when the player is hit

    power.buff | bool | Power is cast upon the caster.

    power.buff_teleport | bool | Power is a teleportation power.

    power.buff_party | bool | Power is cast upon party members

    power.buff_party_power_id | power_id | Only party members that were spawned with this power ID are affected by "buff_party=true". Setting this to 0 will affect all party members.

    power.post_effect | predefined_string, float, duration , float : Effect ID, Magnitude, Duration, Chance to apply | Post effect to apply to target. Duration is in 'ms' or 's'.

    power.post_effect_src | predefined_string, float, duration , float : Effect ID, Magnitude, Duration, Chance to apply | Post effect to apply to caster. Duration is in 'ms' or 's'.

    power.pre_power | power_id, float : Power, Chance to cast | Trigger a power immediately when casting this one.

    power.post_power | power_id, int : Power, Chance to cast | Trigger a power if the hazard did damage. For 'block' type powers, this power will be triggered when the blocker takes damage.

    power.wall_power | power_id, int : Power, Chance to cast | Trigger a power if the hazard hit a wall.

    power.wall_reflect | bool | Moving power will bounce off walls and keep going

    power.spawn_type | predefined_string | For non-transform powers, an enemy is spawned from this category. For transform powers, the caster will transform into a creature from this category.

    power.target_neighbor | int | Target is changed to an adjacent tile within a radius.

    power.spawn_limit | ["unlimited", "fixed", "stat"], int, float, predefined_string : Mode, Entity Level, Ratio, Primary stat | The maximum number of creatures that can be spawned and alive from this power. The need for the last three parameters depends on the mode being used. The "unlimited" mode requires no parameters and will remove any spawn limit requirements. The "fixed" mode takes one parameter as the spawn limit. The "stat" mode also requires the ratio and primary stat ID as parameters. The ratio adjusts the scaling of the spawn limit. For example, spawn_limit=stat,1,2,physical will set the spawn limit to 1/2 the summoner's Physical stat.

    power.spawn_level | ["default", "fixed", "level", "stat"], int, float, predefined_string : Mode, Entity Level, Ratio, Primary stat | The level of spawned creatures. The need for the last three parameters depends on the mode being used. The "default" mode will just use the entity's normal level and doesn't require any additional parameters. The "fixed" mode only requires the entity level as a parameter. The "stat" and "level" modes also require the ratio as a parameter. The ratio adjusts the scaling of the entity level. For example, spawn_level=stat,1,2,physical will set the spawned entity level to 1/2 the summoner's Physical stat. Only the "stat" mode requires the last parameter, which is simply the ID of the primary stat that should be used for scaling.

    power.target_party | bool | Hazard will only affect party members.

    power.target_categories | list(predefined_string) | Hazard will only affect enemies in these categories.

    power.modifier_accuracy | ["multiply", "add", "absolute"], float : Mode, Value | Changes this power's accuracy.

    power.modifier_damage | ["multiply", "add", "absolute"], float, float : Mode, Min, Max | Changes this power's damage. The "Max" value is ignored, except in the case of "absolute" modifiers.

    power.modifier_critical | ["multiply", "add", "absolute"], float : Mode, Value | Changes the chance that this power will land a critical hit.

    power.target_movement_normal | bool | Power can affect entities with normal movement (aka walking on ground)

    power.target_movement_flying | bool | Power can affect flying entities

    power.target_movement_intangible | bool | Power can affect intangible entities

    power.walls_block_aoe | bool | When true, prevents hazard aoe from hitting targets that are behind walls/pits.

    power.script | ["on_cast", "on_hit", "on_wall"], filename : Trigger, Filename | Loads and executes a script file when the trigger is activated.

    power.remove_effect | repeatable(predefined_string, int) : Effect ID, Number of Effect instances | Removes a number of instances of a specific Effect ID. Omitting the number of instances, or setting it to zero, will remove all instances/stacks.

    power.replace_by_effect | repeatable(int, predefined_string, int) : Power ID, Effect ID, Number of Effect instances | If the caster has at least the number of instances of the Effect ID, the defined Power ID will be cast instead.

    power.requires_corpse | ["consume", bool] | If true, a corpse must be targeted for this power to be used. If "consume", then the corpse is also consumed on Power use.

    power.target_nearest | float | Will automatically target the nearest enemy within the specified range.

    power.disable_equip_slots | list(predefined_string) | Passive powers only. A comma separated list of equip slot types to disable when this power is active.


    QuestLog

    Description of quest files in quests/

    name | string | A displayed name for this quest.

    complete_status | string | If this status is set, the quest will be displayed as completed.

    quest.requires_status | list(string) | Quest requires this campaign status

    quest.requires_not_status | list(string) | Quest requires not having this campaign status.

    quest.requires_level | int | Quest requires hero level

    quest.requires_not_level | int | Quest requires not hero level

    quest.requires_currency | int | Quest requires atleast this much currency

    quest.requires_not_currency | int | Quest requires no more than this much currency

    quest.requires_item | list(item_id) | Quest requires specific item (not equipped)

    quest.requires_not_item | list(item_id) | Quest requires not having a specific item (not equipped)

    quest.requires_class | predefined_string | Quest requires this base class

    quest.requires_not_class | predefined_string | Quest requires not this base class

    quest.quest_text | string | Text that gets displayed in the Quest log when this quest is active.


    SDLFontEngine: Font settings

    Description of engine/font_settings.txt

    font.id | string | An identifier used to reference this font.

    font.style | repeatable(["default", predefined_string], filename, int, bool) : Language, Font file, Point size, Blending | Filename, point size, and blend mode of the font to use for this language. Language can be "default" or a 2-letter region code.


    StatBlock: Core stats

    Description of engine/stats.txt, enemies/..., and npcs/...

    speed | float | Movement speed

    cooldown | duration | Cooldown between attacks in 'ms' or 's'.

    cooldown_hit | duration | Duration of cooldown after being hit in 'ms' or 's'.

    stat | stat_id, float : Stat ID, Value | The starting value for this stat.

    stat_per_level | stat_id, float : Stat ID, Value | The value for this stat added per level.

    stat_per_primary | predefined_string, stat_id, float : Primary Stat, Stat ID, Value | The value for this stat added for every point allocated to this primary stat.

    vulnerable | predefined_string, float : Element, Value | (Deprecated in v1.12.91; use a '..._resist' value with 'stat' instead) Percentage weakness to this element.

    power_filter | list(power_id) | Only these powers are allowed to hit this entity.

    categories | list(string) | Categories that this entity belongs to.

    melee_range | float | Determines the distance from the caster that some powers will be placed. For AI entities, it also means the minimum distance from target required to use melee powers.


    StatBlock: Sound effects

    Description of sound effect properties in engine/stats.txt, enemies/..., and npcs/...

    sfx_attack | repeatable(predefined_string, filename) : Animation name, Sound file | Filename of sound effect for the specified attack animation.

    sfx_hit | repeatable(filename) | Filename of sound effect for being hit.

    sfx_die | repeatable(filename) | Filename of sound effect for dying.

    sfx_critdie | repeatable(filename) | Filename of sound effect for dying to a critical hit.

    sfx_block | repeatable(filename) | Filename of sound effect for blocking an incoming hit.

    sfx_levelup | filename | Filename of sound effect for leveling up.

    sfx_lowhp | filename, bool : Sound file, loop | Filename of sound effect for low health warning. Optionally, it can be looped.


    StatBlock: Render layers

    Description of 'render_layers' section in engine/stats.txt, enemies/..., and npcs/...

    render_layers.layer | direction, list(string) : Direction, Layer name(s) | Defines the layer order of slots for a given direction.


    StatBlock: Animation slots

    Description of 'animation_slots' section in enemies/... and npcs/...

    animation_slots.slot | string, filename : Slot name, Animation filename | Assigns an animation to one of the slots defined in the render_layers section.


    StatBlock: Enemies

    Description of enemies in enemies/

    name | string | Name

    humanoid | bool | This creature gives human traits when transformed into, such as the ability to talk with NPCs.

    lifeform | bool | Determines whether or not this entity is referred to as a living thing, such as displaying "Dead" vs "Destroyed" when their HP is 0.

    level | int | Level

    xp | int | XP awarded upon death.

    xp_scaling | filename | XP multiplier table file. See: "XPScaling".

    loot | repeatable(loot) | Possible loot that can be dropped on death.

    loot_count | int, int : Min, Max | Sets the minimum (and optionally, the maximum) amount of loot this creature can drop. Overrides the global drop_max setting.

    defeat_status | string | Campaign status to set upon death.

    convert_status | string | Campaign status to set upon being converted to a player ally.

    first_defeat_loot | item_id | Drops this item upon first death.

    quest_loot | string, string, item_id : Required status, Required not status, Item | Drops this item when campaign status is met.

    flying | bool | Creature can move over gaps/water.

    intangible | bool | Creature can move through walls.

    facing | bool | Creature can turn to face their target.

    waypoint_pause | duration | Duration to wait at each waypoint in 'ms' or 's'.

    turn_delay | duration | Duration it takes for this creature to turn and face their target in 'ms' or 's'.

    chance_pursue | float | Percentage change that the creature will chase their target.

    chance_flee | float | Percentage chance that the creature will run away from their target.

    power | ["melee", "ranged", "beacon", "on_hit", "on_death", "on_half_dead", "on_join_combat", "on_debuff"], power_id, int : State, Power, Chance | A power that has a chance of being triggered in a certain state.

    passive_powers | list(power_id) | A list of passive powers this creature has.

    threat_range | float, float : Engage distance, Stop distance | The first value is the radius of the area this creature will be able to start chasing the hero. The second, optional, value is the radius at which this creature will stop pursuing their target and defaults to double the first value.

    flee_range | float | The radius at which this creature will start moving to a safe distance. Defaults to half of the threat_range.

    combat_style | ["default", "aggressive", "passive"] | How the creature will enter combat. Default is within range of the hero; Aggressive is always in combat; Passive must be attacked to enter combat.

    animations | filename | Filename of an animation definition.

    suppress_hp | bool | Hides the enemy HP bar for this creature.

    flee_duration | duration | The minimum amount of time that this creature will flee. They may flee longer than the specified time.

    flee_cooldown | duration | The amount of time this creature must wait before they can start fleeing again.


    StatBlock: Hero stats

    Description of engine/stats.txt

    max_points_per_stat | int | Maximum points for each primary stat.

    sfx_step | string | An id for a set of step sound effects. See items/step_sounds.txt.

    stat_points_per_level | int | The amount of stat points awarded each level.

    power_points_per_level | int | The amount of power points awarded each level.


    Subtitles

    Description of soundfx/subtitles.txt

    style.text_pos | label | Position and style of the subtitle text.

    style.pos | point | Position of the subtitle text relative to alignment.

    style.align | alignment | Alignment of the subtitle text.

    style.background_color | color, int : Color, Alpha | Color and alpha of the subtitle background rectangle.

    subtitle.id | filename | Filename of the sound file that will trigger this subtitle.

    subtitle.text | string | The subtitle text that will be displayed.


    TileSet

    Description of tilesets in tilesets/

    tileset.img | filename | Filename of a tile sheet image.

    tileset.tile | int, int, int, int, int, int, int : Index, X, Y, Width, Height, X offset, Y offset | A single tile definition.

    tileset.animation | list(int, int, int, duration) : Tile index, X, Y, duration | An animation for a tile. Durations are in 'ms' or 's'.


    XPScaling

    Description of enemies/xp_scaling/

    absolute.level | int, float : Level, Multiplier | The multiplier that will be applied to the rewarded XP when an enemy is at a specific level. If the enemy's level is outside the defined scaling levels, the min or max level is used (whichever is closer).

    relative.level | int, float : Level, Multiplier | The multiplier that will be applied to the rewarded XP when an enemy's level is X levels apart from the player's level. If the enemy's level is outside the defined scaling levels, the min or max level is used (whichever is closer).


    Predefined Strings

    Below are some strings that are used in various attributes.


    EffectManager

    Description of "type" in powers/effects.txt

    death_sentence | Causes sudden death at the end of the effect duration.

    damage | Damage per second

    damage_percent | Damage per second (percentage of max HP)

    hpot | HP restored per second

    hpot_percent | HP restored per second (percentage of max HP)

    mpot | MP restored per second

    mpot_percent | MP restored per second (percentage of max MP)

    speed | Changes movement speed. A magnitude of 100 is 100% speed (aka normal speed).

    attack_speed | Changes attack speed. A magnitude of 100 is 100% speed (aka normal speed).

    resist_all | Applies a bonus to all of the non-elemental resistance stats.

    stun | Can't move or attack. Being attacked breaks stun.

    revive | Revives the player. Typically attached to a power that triggers when the player dies.

    convert | Causes an enemy or an ally to switch allegiance

    fear | Causes enemies to run away

    knockback | Pushes the target away from the source caster. Speed is the given value divided by the framerate cap.

    ${STAT} | Increases ${STAT}, where ${STAT} is any valid stat_id.

    ${PRIMARYSTAT} | Increases ${PRIMARYSTAT}, where ${PRIMARYSTAT} is any of the primary stats defined in engine/primary_stats.txt. Example: physical

    shield | Create a damage absorbing barrier based on Mental damage stat. Duration is ignored.

    heal | Restore HP based on Mental damage stat.


    ItemManager

    Description of "bonus" attribute in items/items.txt

    speed | Movement speed. A value of 100 is 100% speed (aka normal speed).

    attack_speed | Attack animation speed. A value of 100 is 100% speed (aka normal speed).

    ${STAT} | Increases ${STAT}, where ${STAT} is any valid stat_id.

    ${PRIMARYSTAT} | Increases ${PRIMARYSTAT}, where ${PRIMARYSTAT} is any of the primary stats defined in engine/primary_stats.txt. Example: physical


    Stats

    Description of the base stats which may be used wherever a stat_id is required.

    hp | Hit points

    hp_regen | HP restored per minute

    mp | Magic points

    mp_regen | MP restored per minute

    accuracy | Accuracy %. Higher values mean less likely to miss.

    avoidance | Avoidance %. Higher values means more likely to not get hit.

    absorb_min | Minimum damage absorption

    absorb_max | Maximum damage absorption

    crit | Critical hit chance %

    xp_gain | Percentage boost to the amount of experience points gained per kill.

    currency_find | Percentage boost to the amount of gold dropped per loot event.

    item_find | Increases the chance of finding items in loot.

    stealth | Decrease the distance required to alert enemies by %

    poise | Reduced % chance of entering "hit" animation when damaged

    reflect_chance | Percentage chance to reflect missiles

    return_damage | Deals a percentage of the damage taken back to the attacker

    hp_steal | Percentage of HP stolen when damaging a target

    mp_steal | Percentage of MP stolen when damaging a target

    resist_damage_over_time | Percentage chance that damage-over-time effects will be negated

    resist_slow | Percentage chance that slow effects will be negated

    resist_stun | Percentage chance that stun effects will be negated

    resist_knockback | Percentage chance that knockback effects will be negated

    resist_stat_debuff | Percentage chance that stat debuff effects will be negated

    resist_damage_reflect | Percentage chance that damage reflection will be negated

    resist_hp_steal | Percentage chance that HP steal will be negated

    resist_mp_steal | Percentage chance that MP steal will be negated

    flare-engine-1.14/docs/code-conventions.html000066400000000000000000000071161434514645000211010ustar00rootroot00000000000000 FLARE Documentation - Code Conventions

    Contributing Code

    The #1 rule of this project: all non-trivial code changes must be discussed first!

    Years of careful decision-making have gone into all parts of this project. Some designs are awaiting planned future changes. If you submit a large patch without knowing how it will affect past and future code, it will be rejected.

    Is my code non-trivial? If it touches multiple functions or files, probably so.

    Ways to discuss a desired code change:

    • Email me (clintbellanger@gmail.com)
    • Open an Issue
    • Join us on IRC (#flarerpg at freenode)

    Pull Requests

    When possible, use small pull requests that keep the engine in a stable state. It is far better to have many small pull requests than to wait until your change is too large to merge.

    If you're working on more than one feature, submit pull requests separately for these. Some times we need to accept one change and reject the other; if your pull request is all combined I have to reject the entire thing.

    Cross-platform considerations

    To the best of our ability, we want the code to work on Windows, OSX, Linux and more. We try to isolate platform-specific code and use C preprocessor directives when necessary.

    OOP as a last resort

    My code is simple (some would say not elegant). I start by coding it the simplest possible way that would work. I have a deep disdain for overengineering.

    Action RPGs are a really simple genre. We don't need fully OOP Entity system to create these games.

    We have introduced OOP as needed, when the solution makes much more sense than the added complexity. We won't needlessly refactor working code.

    No C++11 / C++0x

    Maybe we'll allow this kind of code in the future. But Flare has a presence on obscure platforms; I can't guarantee that the compilers have kept up.

    Indentation

    I try to use tabs. I don't really care though, as long as a single file is internally consistent.

    Naming Conventions

    Here are the basic conventions, which are not really consistent. Not a big deal, I sometimes clean these up as I go.

    • ClassName::functionName
    • ClassName::class_variable
    • local_variable
    • ENUM_OR_CONSTANT

    Commenting

    I try to use Javadoc style comments on functions, especially if they're non-obvious.

    I try to avoid block comments inside functions (so it's easy to block comment out an entire function).

    Namespaces

    Avoid polluting the project's namespace. This means no using namespace std. Instead, prefix the appropriate names with the std:: syntax (e.g. std::vector, std::string, std::min(), etc)

    flare-engine-1.14/docs/gen/000077500000000000000000000000001434514645000155025ustar00rootroot00000000000000flare-engine-1.14/docs/gen/attribute-reference.md000066400000000000000000000061041434514645000217640ustar00rootroot00000000000000### Attribute description Attribute descriptions are in the following format: section.attributename, `valuetype`, short attribute description **This reference is generated from in-source documentation, so please do not edit it directly.** #### Valuetype syntax `[val1, ...]`, Multiple values between a set of square brackets denotes possible types/values for a single field. `"..."`, Anything between double quotes is a string literal. This is mostly used in conjunction with the above square brackets syntax. `list(...)`, Lists are a series of values that can repeat on a single line, separated by commas/semicolons. Example of list(int, bool): "1,false;2,true" `repeatable(...)`, Repeatable keys can be used for multiple lines. An example of this would be an item with multiple "bonus" lines. #### List of available valuetypes `bool`, a string value of `true` or `false` `int`, a signed integer value `string`, a text string value `float`, a floating point number `item_id`, specifies an integer item identifer (greater than 0) `power_id`, specifies an integer power identifer (greater than 0) `icon_id`, specifies an integer icon identifer (greater than or equal to 0) `point`, defined as: `int, int : X, Y` `rectangle`, defined as: `int, int, int, int : X, Y, Width, Height` `filename`, a string path to a file relative to the base of the mod directory (e.g. "animations/hero.txt") `color`, defined as: `int, int, int : Red, Green, Blue` `predefined_string`, same as a string, but uses a value defined elsewhere `alignment`, defined as: `["topleft", "top", "topright", "left", "center", "right", "bottomleft", "bottom", "bottomright", "frame_topleft", "frame_top", "frame_topright", "frame_left", "frame_center", "frame_right", "frame_bottomleft", "frame_bottom", "frame_bottomright"]` `direction`, defined as: `["N", "NE", "E", "SE", "S", "SW", "W", "NW", int]`. If defined as an integer, the value must be between 0-7 inclusive, mapping to `["W", "NW", "N", "NE", "E", "SE", "S", "SW"]` respectively. `duration`, durations can be specified in seconds and milliseconds with integer suffix s, ms (eg. 20s, 20000ms) `label`, defined as: `"hidden"` **or** `int, int, ["left", "right", "center"], ["top", "center", "bottom"], string : X, Y, Justify, Vertical Align, Font style`. The font style can be any style defined in engine/font\_settings.txt. `loot`, defined as: `filename` **or** `["currency", item_id], ["fixed", float], int, int : Item, Drop chance, Min quantity, Max quantity`. There is a limitation when defining as part of a list(...): filenames can only be used in the first list element. `version`, defined as: a string of three numbers, separated by dots (e.g. "1.2.03") `raw`, This is plain text, including line breaks. It is used only for map layer data. `stat_id`, a string that can be any of the following: A base stat (e.g. `hp` or `avoidance`); A min or max damage type from `engine/damage_types.txt` (e.g. `dmg_melee_min` or `dmg_ment_max`); An elemental resistance from `engine/elements.txt`. To use an element as a resistance, `_resist` is appened to the element id (e.g. `fire_resist`) flare-engine-1.14/docs/gen/attribute-reference.sh000077500000000000000000000005531434514645000220030ustar00rootroot00000000000000#!/bin/bash HTMLFILE="../attribute-reference.html" PAGETITLE="Attribute Reference" sed -e "s/PAGETITLE/$PAGETITLE/g" header.txt > "$HTMLFILE" markdown -f -smarty "attribute-reference.md" >> "$HTMLFILE" cd ../../ ./extract_xml.sh | xsltproc wiki.xslt - | sed -e '1,3d' | markdown -f -smarty >> "docs/gen/$HTMLFILE" cd "docs/gen" cat footer.txt >> "$HTMLFILE" flare-engine-1.14/docs/gen/code-conventions.sh000077500000000000000000000004641434514645000213220ustar00rootroot00000000000000#!/bin/bash HTMLFILE="../code-conventions.html" PAGETITLE="Code Conventions" WIKIPATH="../../../flare-engine.wiki" if [ -d "$WIKIPATH" ]; then sed -e "s/PAGETITLE/$PAGETITLE/g" header.txt > "$HTMLFILE" markdown -f -smarty "$WIKIPATH/Code-Conventions.md" >> "$HTMLFILE" cat footer.txt >> "$HTMLFILE" fi flare-engine-1.14/docs/gen/footer.txt000066400000000000000000000001671434514645000175450ustar00rootroot00000000000000 flare-engine-1.14/docs/gen/gen_all.sh000077500000000000000000000004261434514645000174440ustar00rootroot00000000000000#!/bin/bash DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) cd "$DIR" . ./attribute-reference.sh # copy pages from wiki # these scripts assume you have a "flare-engine.wiki" directory next to your # "flare-engine" directory . ./translations.sh . ./code-conventions.sh flare-engine-1.14/docs/gen/header.txt000066400000000000000000000012221434514645000174700ustar00rootroot00000000000000 FLARE Documentation - PAGETITLE
    flare-engine-1.14/docs/gen/translations.sh000077500000000000000000000004501434514645000205610ustar00rootroot00000000000000#!/bin/bash HTMLFILE="../translations.html" PAGETITLE="Translations" WIKIPATH="../../../flare-engine.wiki" if [ -d "$WIKIPATH" ]; then sed -e "s/PAGETITLE/$PAGETITLE/g" header.txt > "$HTMLFILE" markdown -f -smarty "$WIKIPATH/Translations.md" >> "$HTMLFILE" cat footer.txt >> "$HTMLFILE" fi flare-engine-1.14/docs/translations.html000066400000000000000000000136531434514645000203500ustar00rootroot00000000000000 FLARE Documentation - Translations

    Transifex

    Our preferred method of handling translations is through Transifex. However, you're more than welcome to edit the local files and submit them via pull request or email.

    Translation file locations

    The main engine translation file is at:

    /mods/default/languages/engine.pot
    

    This is the simplest and most important file to translate. It contains the common engine and interface text. Having this text translated at least makes the interface usable for readers of that language.

    The data translation files are at:

    /mods/[modname]/languages/data.pot
    

    Once the basic engine text is translated, these data files are the next files to translate. Note that not all mods have a translation file (example: mods that only update graphics).

    In these language folders you'll see translations of these pot files. Each language has its own .po file. Example:

    /mods/default/languages/engine.it.po
    /mods/fantasycore/languages/data.ru.po
    

    The filenames for these language translations use the ISO standard 2-letter language codes (639-1). If the language only has a 639-2 code, that is permitted instead.

    Editing an existing po file

    Before editing an existing translation, check with the original translators to make sure they're not already working on this. Example, here we list the previous translators for Flare-game: https://github.com/flareteam/flare-game/wiki/Credits#translations

    We use a very small subset of the gettext convention for Flare translations. Most .po file editing apps should work. In a pinch, a plain-text editor should work (but be sure to test in-game, as syntax errors are more possible).

    Translation entries have two lines. The msgid is the original text and the msgstr is your translation. Example from engine.it.po:

    #: ../../../src/Avatar.cpp:633
    msgid "You are defeated. Game over! Press Enter to exit to Title."
    msgstr "La Morte ti ha raggiunto. Game over! Premere Invio per tornare al Titolo."
    

    Any line that starts with # is a comment. Often these comments refer to the source file of the text. You might want to look at the source file if you're having trouble figuring out the context of this message.

    The lines that begin with msgid and msgstr always start with those keywords, followed by one blank space, followed by the entire message in double-quotes (").

    Sometimes the msgid contains a % sign followed by a letter, like this:

    #: ../../../src/Avatar.cpp:444
    msgid "Congratulations, you have reached level %d!"
    msgstr "Felicitaciones, has alcanzado el nivel %d!"
    

    This means some value (usually a number) is inserted into the text at that position. Your translation should include the same % sign followed by the same letter.

    Translating is as simple as editing the text inside the quotes of the msgstr line. Do not modify the msgid lines. The only other thing your edited .po file should include is updated header information e.g. the revision date and last translator.

    Creating a new .po file

    Copy the engine.pot (or data.pot) file and rename it to engine.xx.po, where xx is the new language's code. Then edit your new .po file. First fill in the header information, then fill in the msgstr values.

    If you get confused on any one point, look at other .po files in the same folder for examples.

    Enabling a new language in the engine

    If you're creating a new language file, you'll especially want to test it by playing the game with your language enabled. Edit this file and add your new language entry:

    mods/default/engine/languages.txt
    

    And save the file. When running the game use the Configuration menu, Interface tab to select your new language.

    Submitting your new .po files

    If you're already familiar with GitHub you could fork the engine or game and send your update as a Pull Request.

    Otherwise it's fine to email your new .po file(s) to the engine maintainer (Justin Jacobs or Clint Bellanger) or the specific game maintainers.

    Please include your name or preferred nickname to be included in the credits.

    Note that we're only accepting human-translated text. Please don't submit a translation that was simply run through machine translation.

    Tools

    Here is a list of .po file editing tools, if you don't want to use a plain text editor.

    • Poedit - simple cross-platform editor of gettext catalogs.
    • Virtaal - easy translation tool that supports many localization formats.
    • GTranslator - powerful translation tool integrated with GNOME desktop.
    • Lokalize - editor of gettext catalogs integrated with KDE desktop.
    flare-engine-1.14/flare-android-project/000077500000000000000000000000001434514645000201545ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/README.android000066400000000000000000000012171434514645000224540ustar00rootroot00000000000000To build for Android do next steps: 1. Install Java Development Kit (x86 version) 2. Install Android Studio and run it 3. Open an existing Android Studio project and navigate to the directory containing this README 4. Install Android NDK from the Android SDK Tools if not already installed 5. Install SDK Platform for your Android version 6. Set-up SDL libraries 1. Download SDL2, SDL2_image, SDL2_mixer and SDL2_ttf and extract them to \jni\ folder (so it will be like jni\SDL2\src\SDL.c) 2. In SDL2_image/Android.mk, set SUPPORT_JPG and SUPPORT_WEBP to false. 3. In SDL2_mixer/Android.mk, set SUPPORT_MOD_MIKMOD and SUPPORT_MP3_SMPEG to false. flare-engine-1.14/flare-android-project/app/000077500000000000000000000000001434514645000207345ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/build.gradle000066400000000000000000000026651434514645000232240ustar00rootroot00000000000000import org.gradle.internal.os.OperatingSystem; apply plugin: 'com.android.application' android { compileSdkVersion 29 // Each version of the Android Gradle Plugin now has a default version of the build tools //buildToolsVersion '29.0.2' // don't use Android.mk generated by Android Studio / Gradle sourceSets.main.jni.srcDirs = [] sourceSets.main.jniLibs.srcDirs = ['src/main/libs'] defaultConfig { applicationId "org.flare.app" minSdkVersion 16 targetSdkVersion 29 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } } // call regular ndk-build script from app directory task ndkBuild(type: Exec) { if (OperatingSystem.current().isWindows()) { commandLine 'ndk-build.cmd', '-C', file('src/main').absolutePath, '-j', Runtime.runtime.availableProcessors() } else { commandLine 'ndk-build', '-C', file('src/main').absolutePath, '-j', Runtime.runtime.availableProcessors() } } task ndkClean(type: Exec) { if (OperatingSystem.current().isWindows()) { commandLine 'ndk-build.cmd', '-C', file('src/main').absolutePath, 'clean' } else { commandLine 'ndk-build', '-C', file('src/main').absolutePath, 'clean' } } tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn ndkBuild } clean.dependsOn 'ndkClean'flare-engine-1.14/flare-android-project/app/src/000077500000000000000000000000001434514645000215235ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/000077500000000000000000000000001434514645000224475ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/AndroidManifest.xml000066400000000000000000000027501434514645000262440ustar00rootroot00000000000000 flare-engine-1.14/flare-android-project/app/src/main/java/000077500000000000000000000000001434514645000233705ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/java/org/000077500000000000000000000000001434514645000241575ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/java/org/flare/000077500000000000000000000000001434514645000252505ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/java/org/flare/app/000077500000000000000000000000001434514645000260305ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/java/org/flare/app/FLARE.java000066400000000000000000000035271434514645000275330ustar00rootroot00000000000000package org.flare.app; import org.libsdl.app.SDLActivity; import android.Manifest; import android.content.pm.PackageManager; import android.os.*; /* * A sample wrapper class that just calls SDLActivity */ public class FLARE extends SDLActivity { protected void onCreate(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (super.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { super.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } } super.onCreate(savedInstanceState); } protected void onDestroy() { super.onDestroy(); } public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // read/write permission granted, so we need to restart the app // the dialog we pop up when unable to find mods leaks during the restart, so it ends up visible for a brief moment if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { super.recreate(); super.initialize(); super.onCreate(null); } else { super.finishAndRemoveTask(); super.initialize(); // need a clean state if the app is switched to again } } } protected String[] getLibraries() { return new String[] { "SDL2", "SDL2_image", "mpg123", "SDL2_mixer", //"SDL2_net", "SDL2_ttf", "main" }; } } flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/000077500000000000000000000000001434514645000254305ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/app/000077500000000000000000000000001434514645000262105ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/app/HIDDevice.java000066400000000000000000000011571434514645000306030ustar00rootroot00000000000000package org.libsdl.app; import android.hardware.usb.UsbDevice; interface HIDDevice { public int getId(); public int getVendorId(); public int getProductId(); public String getSerialNumber(); public int getVersion(); public String getManufacturerName(); public String getProductName(); public UsbDevice getDevice(); public boolean open(); public int sendFeatureReport(byte[] report); public int sendOutputReport(byte[] report); public boolean getFeatureReport(byte[] report); public void setFrozen(boolean frozen); public void close(); public void shutdown(); } HIDDeviceBLESteamController.java000066400000000000000000000571231434514645000341110ustar00rootroot00000000000000flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/apppackage org.libsdl.app; import android.content.Context; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothGattService; import android.hardware.usb.UsbDevice; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.os.*; //import com.android.internal.util.HexDump; import java.lang.Runnable; import java.util.Arrays; import java.util.LinkedList; import java.util.UUID; class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { private static final String TAG = "hidapi"; private HIDDeviceManager mManager; private BluetoothDevice mDevice; private int mDeviceId; private BluetoothGatt mGatt; private boolean mIsRegistered = false; private boolean mIsConnected = false; private boolean mIsChromebook = false; private boolean mIsReconnecting = false; private boolean mFrozen = false; private LinkedList mOperations; GattOperation mCurrentOperation = null; private Handler mHandler; private static final int TRANSPORT_AUTO = 0; private static final int TRANSPORT_BREDR = 1; private static final int TRANSPORT_LE = 2; private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; static class GattOperation { private enum Operation { CHR_READ, CHR_WRITE, ENABLE_NOTIFICATION } Operation mOp; UUID mUuid; byte[] mValue; BluetoothGatt mGatt; boolean mResult = true; private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { mGatt = gatt; mOp = operation; mUuid = uuid; } private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { mGatt = gatt; mOp = operation; mUuid = uuid; mValue = value; } public void run() { // This is executed in main thread BluetoothGattCharacteristic chr; switch (mOp) { case CHR_READ: chr = getCharacteristic(mUuid); //Log.v(TAG, "Reading characteristic " + chr.getUuid()); if (!mGatt.readCharacteristic(chr)) { Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); mResult = false; break; } mResult = true; break; case CHR_WRITE: chr = getCharacteristic(mUuid); //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); chr.setValue(mValue); if (!mGatt.writeCharacteristic(chr)) { Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); mResult = false; break; } mResult = true; break; case ENABLE_NOTIFICATION: chr = getCharacteristic(mUuid); //Log.v(TAG, "Writing descriptor of " + chr.getUuid()); if (chr != null) { BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); if (cccd != null) { int properties = chr.getProperties(); byte[] value; if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; } else { Log.e(TAG, "Unable to start notifications on input characteristic"); mResult = false; return; } mGatt.setCharacteristicNotification(chr, true); cccd.setValue(value); if (!mGatt.writeDescriptor(cccd)) { Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); mResult = false; return; } mResult = true; } } } } public boolean finish() { return mResult; } private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { BluetoothGattService valveService = mGatt.getService(steamControllerService); if (valveService == null) return null; return valveService.getCharacteristic(uuid); } static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { return new GattOperation(gatt, Operation.CHR_READ, uuid); } static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); } static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); } } public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { mManager = manager; mDevice = device; mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); mIsRegistered = false; mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); mOperations = new LinkedList(); mHandler = new Handler(Looper.getMainLooper()); mGatt = connectGatt(); // final HIDDeviceBLESteamController finalThis = this; // mHandler.postDelayed(new Runnable() { // @Override // public void run() { // finalThis.checkConnectionForChromebookIssue(); // } // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); } public String getIdentifier() { return String.format("SteamController.%s", mDevice.getAddress()); } public BluetoothGatt getGatt() { return mGatt; } // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead // of TRANSPORT_LE. Let's force ourselves to connect low energy. private BluetoothGatt connectGatt(boolean managed) { if (Build.VERSION.SDK_INT >= 23) { try { return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); } catch (Exception e) { return mDevice.connectGatt(mManager.getContext(), managed, this); } } else { return mDevice.connectGatt(mManager.getContext(), managed, this); } } private BluetoothGatt connectGatt() { return connectGatt(false); } protected int getConnectionState() { Context context = mManager.getContext(); if (context == null) { // We are lacking any context to get our Bluetooth information. We'll just assume disconnected. return BluetoothProfile.STATE_DISCONNECTED; } BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); if (btManager == null) { // This device doesn't support Bluetooth. We should never be here, because how did // we instantiate a device to start with? return BluetoothProfile.STATE_DISCONNECTED; } return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); } public void reconnect() { if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { mGatt.disconnect(); mGatt = connectGatt(); } } protected void checkConnectionForChromebookIssue() { if (!mIsChromebook) { // We only do this on Chromebooks, because otherwise it's really annoying to just attempt // over and over. return; } int connectionState = getConnectionState(); switch (connectionState) { case BluetoothProfile.STATE_CONNECTED: if (!mIsConnected) { // We are in the Bad Chromebook Place. We can force a disconnect // to try to recover. Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); break; } else if (!isRegistered()) { if (mGatt.getServices().size() > 0) { Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); probeService(this); } else { Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); break; } } else { Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); return; } break; case BluetoothProfile.STATE_DISCONNECTED: Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); break; case BluetoothProfile.STATE_CONNECTING: Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); break; } final HIDDeviceBLESteamController finalThis = this; mHandler.postDelayed(new Runnable() { @Override public void run() { finalThis.checkConnectionForChromebookIssue(); } }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); } private boolean isRegistered() { return mIsRegistered; } private void setRegistered() { mIsRegistered = true; } private boolean probeService(HIDDeviceBLESteamController controller) { if (isRegistered()) { return true; } if (!mIsConnected) { return false; } Log.v(TAG, "probeService controller=" + controller); for (BluetoothGattService service : mGatt.getServices()) { if (service.getUuid().equals(steamControllerService)) { Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { if (chr.getUuid().equals(inputCharacteristic)) { Log.v(TAG, "Found input characteristic"); // Start notifications BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); if (cccd != null) { enableNotification(chr.getUuid()); } } } return true; } } if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); mIsConnected = false; mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); } return false; } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// private void finishCurrentGattOperation() { GattOperation op = null; synchronized (mOperations) { if (mCurrentOperation != null) { op = mCurrentOperation; mCurrentOperation = null; } } if (op != null) { boolean result = op.finish(); // TODO: Maybe in main thread as well? // Our operation failed, let's add it back to the beginning of our queue. if (!result) { mOperations.addFirst(op); } } executeNextGattOperation(); } private void executeNextGattOperation() { synchronized (mOperations) { if (mCurrentOperation != null) return; if (mOperations.isEmpty()) return; mCurrentOperation = mOperations.removeFirst(); } // Run in main thread mHandler.post(new Runnable() { @Override public void run() { synchronized (mOperations) { if (mCurrentOperation == null) { Log.e(TAG, "Current operation null in executor?"); return; } mCurrentOperation.run(); // now wait for the GATT callback and when it comes, finish this operation } } }); } private void queueGattOperation(GattOperation op) { synchronized (mOperations) { mOperations.add(op); } executeNextGattOperation(); } private void enableNotification(UUID chrUuid) { GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid); queueGattOperation(op); } public void writeCharacteristic(UUID uuid, byte[] value) { GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); queueGattOperation(op); } public void readCharacteristic(UUID uuid) { GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); queueGattOperation(op); } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////// BluetoothGattCallback overridden methods ////////////////////////////////////////////////////////////////////////////////////////////////////// public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); mIsReconnecting = false; if (newState == 2) { mIsConnected = true; // Run directly, without GattOperation if (!isRegistered()) { mHandler.post(new Runnable() { @Override public void run() { mGatt.discoverServices(); } }); } } else if (newState == 0) { mIsConnected = false; } // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. } public void onServicesDiscovered(BluetoothGatt gatt, int status) { //Log.v(TAG, "onServicesDiscovered status=" + status); if (status == 0) { if (gatt.getServices().size() == 0) { Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); mIsReconnecting = true; mIsConnected = false; gatt.disconnect(); mGatt = connectGatt(false); } else { probeService(this); } } } public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue()); } finishCurrentGattOperation(); } public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); if (characteristic.getUuid().equals(reportCharacteristic)) { // Only register controller with the native side once it has been fully configured if (!isRegistered()) { Log.v(TAG, "Registering Steam Controller with ID: " + getId()); mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0); setRegistered(); } } finishCurrentGattOperation(); } public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // Enable this for verbose logging of controller input reports //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) { mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); } } public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { //Log.v(TAG, "onDescriptorRead status=" + status); } public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); if (chr.getUuid().equals(inputCharacteristic)) { boolean hasWrittenInputDescriptor = true; BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic); if (reportChr != null) { Log.v(TAG, "Writing report characteristic to enter valve mode"); reportChr.setValue(enterValveMode); gatt.writeCharacteristic(reportChr); } } finishCurrentGattOperation(); } public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { //Log.v(TAG, "onReliableWriteCompleted status=" + status); } public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { //Log.v(TAG, "onReadRemoteRssi status=" + status); } public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { //Log.v(TAG, "onMtuChanged status=" + status); } ////////////////////////////////////////////////////////////////////////////////////////////////////// //////// Public API ////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public int getId() { return mDeviceId; } @Override public int getVendorId() { // Valve Corporation final int VALVE_USB_VID = 0x28DE; return VALVE_USB_VID; } @Override public int getProductId() { // We don't have an easy way to query from the Bluetooth device, but we know what it is final int D0G_BLE2_PID = 0x1106; return D0G_BLE2_PID; } @Override public String getSerialNumber() { // This will be read later via feature report by Steam return "12345"; } @Override public int getVersion() { return 0; } @Override public String getManufacturerName() { return "Valve Corporation"; } @Override public String getProductName() { return "Steam Controller"; } @Override public UsbDevice getDevice() { return null; } @Override public boolean open() { return true; } @Override public int sendFeatureReport(byte[] report) { if (!isRegistered()) { Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!"); if (mIsConnected) { probeService(this); } return -1; } // We need to skip the first byte, as that doesn't go over the air byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report)); writeCharacteristic(reportCharacteristic, actual_report); return report.length; } @Override public int sendOutputReport(byte[] report) { if (!isRegistered()) { Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!"); if (mIsConnected) { probeService(this); } return -1; } //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report)); writeCharacteristic(reportCharacteristic, report); return report.length; } @Override public boolean getFeatureReport(byte[] report) { if (!isRegistered()) { Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!"); if (mIsConnected) { probeService(this); } return false; } //Log.v(TAG, "getFeatureReport"); readCharacteristic(reportCharacteristic); return true; } @Override public void close() { } @Override public void setFrozen(boolean frozen) { mFrozen = frozen; } @Override public void shutdown() { close(); BluetoothGatt g = mGatt; if (g != null) { g.disconnect(); g.close(); mGatt = null; } mManager = null; mIsRegistered = false; mIsConnected = false; mOperations.clear(); } } flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java000066400000000000000000000636671434514645000321140ustar00rootroot00000000000000package org.libsdl.app; import android.app.Activity; import android.app.AlertDialog; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.os.Build; import android.util.Log; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.usb.*; import android.os.Handler; import android.os.Looper; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; public class HIDDeviceManager { private static final String TAG = "hidapi"; private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION"; private static HIDDeviceManager sManager; private static int sManagerRefCount = 0; public static HIDDeviceManager acquire(Context context) { if (sManagerRefCount == 0) { sManager = new HIDDeviceManager(context); } ++sManagerRefCount; return sManager; } public static void release(HIDDeviceManager manager) { if (manager == sManager) { --sManagerRefCount; if (sManagerRefCount == 0) { sManager.close(); sManager = null; } } } private Context mContext; private HashMap mDevicesById = new HashMap(); private HashMap mBluetoothDevices = new HashMap(); private int mNextDeviceId = 0; private SharedPreferences mSharedPreferences = null; private boolean mIsChromebook = false; private UsbManager mUsbManager; private Handler mHandler; private BluetoothManager mBluetoothManager; private List mLastBluetoothDevices; private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); handleUsbDeviceAttached(usbDevice); } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); handleUsbDeviceDetached(usbDevice); } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); } } }; private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // Bluetooth device was connected. If it was a Steam Controller, handle it if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Log.d(TAG, "Bluetooth device connected: " + device); if (isSteamController(device)) { connectBluetoothDevice(device); } } // Bluetooth device was disconnected, remove from controller manager (if any) if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Log.d(TAG, "Bluetooth device disconnected: " + device); disconnectBluetoothDevice(device); } } }; private HIDDeviceManager(final Context context) { mContext = context; HIDDeviceRegisterCallback(); mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); // if (shouldClear) { // SharedPreferences.Editor spedit = mSharedPreferences.edit(); // spedit.clear(); // spedit.commit(); // } // else { mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); } } public Context getContext() { return mContext; } public int getDeviceIDForIdentifier(String identifier) { SharedPreferences.Editor spedit = mSharedPreferences.edit(); int result = mSharedPreferences.getInt(identifier, 0); if (result == 0) { result = mNextDeviceId++; spedit.putInt("next_device_id", mNextDeviceId); } spedit.putInt(identifier, result); spedit.commit(); return result; } private void initializeUSB() { mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); if (mUsbManager == null) { return; } /* // Logging for (UsbDevice device : mUsbManager.getDeviceList().values()) { Log.i(TAG,"Path: " + device.getDeviceName()); Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); Log.i(TAG,"Product: " + device.getProductName()); Log.i(TAG,"ID: " + device.getDeviceId()); Log.i(TAG,"Class: " + device.getDeviceClass()); Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); Log.i(TAG,"Vendor ID " + device.getVendorId()); Log.i(TAG,"Product ID: " + device.getProductId()); Log.i(TAG,"Interface count: " + device.getInterfaceCount()); Log.i(TAG,"---------------------------------------"); // Get interface details for (int index = 0; index < device.getInterfaceCount(); index++) { UsbInterface mUsbInterface = device.getInterface(index); Log.i(TAG," ***** *****"); Log.i(TAG," Interface index: " + index); Log.i(TAG," Interface ID: " + mUsbInterface.getId()); Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); // Get endpoint details for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) { UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); Log.i(TAG," ++++ ++++ ++++"); Log.i(TAG," Endpoint index: " + epi); Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); Log.i(TAG," Direction: " + mEndpoint.getDirection()); Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); Log.i(TAG," Interval: " + mEndpoint.getInterval()); Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); Log.i(TAG," Type: " + mEndpoint.getType()); } } } Log.i(TAG," No more devices connected."); */ // Register for USB broadcasts and permission completions IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); mContext.registerReceiver(mUsbBroadcast, filter); for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { handleUsbDeviceAttached(usbDevice); } } UsbManager getUSBManager() { return mUsbManager; } private void shutdownUSB() { try { mContext.unregisterReceiver(mUsbBroadcast); } catch (Exception e) { // We may not have registered, that's okay } } private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { return true; } if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { return true; } return false; } private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { final int XB360_IFACE_SUBCLASS = 93; final int XB360_IFACE_PROTOCOL = 1; // Wired final int XB360W_IFACE_PROTOCOL = 129; // Wireless final int[] SUPPORTED_VENDORS = { 0x0079, // GPD Win 2 0x044f, // Thrustmaster 0x045e, // Microsoft 0x046d, // Logitech 0x056e, // Elecom 0x06a3, // Saitek 0x0738, // Mad Catz 0x07ff, // Mad Catz 0x0e6f, // PDP 0x0f0d, // Hori 0x1038, // SteelSeries 0x11c9, // Nacon 0x12ab, // Unknown 0x1430, // RedOctane 0x146b, // BigBen 0x1532, // Razer Sabertooth 0x15e4, // Numark 0x162e, // Joytech 0x1689, // Razer Onza 0x1949, // Lab126, Inc. 0x1bad, // Harmonix 0x20d6, // PowerA 0x24c6, // PowerA 0x2c22, // Qanba }; if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { int vendor_id = usbDevice.getVendorId(); for (int supportedVid : SUPPORTED_VENDORS) { if (vendor_id == supportedVid) { return true; } } } return false; } private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { final int XB1_IFACE_SUBCLASS = 71; final int XB1_IFACE_PROTOCOL = 208; final int[] SUPPORTED_VENDORS = { 0x045e, // Microsoft 0x0738, // Mad Catz 0x0e6f, // PDP 0x0f0d, // Hori 0x1532, // Razer Wildcat 0x20d6, // PowerA 0x24c6, // PowerA 0x2dc8, /* 8BitDo */ 0x2e24, // Hyperkin }; if (usbInterface.getId() == 0 && usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { int vendor_id = usbDevice.getVendorId(); for (int supportedVid : SUPPORTED_VENDORS) { if (vendor_id == supportedVid) { return true; } } } return false; } private void handleUsbDeviceAttached(UsbDevice usbDevice) { connectHIDDeviceUSB(usbDevice); } private void handleUsbDeviceDetached(UsbDevice usbDevice) { List devices = new ArrayList(); for (HIDDevice device : mDevicesById.values()) { if (usbDevice.equals(device.getDevice())) { devices.add(device.getId()); } } for (int id : devices) { HIDDevice device = mDevicesById.get(id); mDevicesById.remove(id); device.shutdown(); HIDDeviceDisconnected(id); } } private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { for (HIDDevice device : mDevicesById.values()) { if (usbDevice.equals(device.getDevice())) { boolean opened = false; if (permission_granted) { opened = device.open(); } HIDDeviceOpenResult(device.getId(), opened); } } } private void connectHIDDeviceUSB(UsbDevice usbDevice) { synchronized (this) { int interface_mask = 0; for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { UsbInterface usbInterface = usbDevice.getInterface(interface_index); if (isHIDDeviceInterface(usbDevice, usbInterface)) { // Check to see if we've already added this interface // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive int interface_id = usbInterface.getId(); if ((interface_mask & (1 << interface_id)) != 0) { continue; } interface_mask |= (1 << interface_id); HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); int id = device.getId(); mDevicesById.put(id, device); HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol()); } } } } private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); if (Build.VERSION.SDK_INT <= 30 && mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); return; } if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) { Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); return; } // Find bonded bluetooth controllers and create SteamControllers for them mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { // This device doesn't support Bluetooth. return; } BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); if (btAdapter == null) { // This device has Bluetooth support in the codebase, but has no available adapters. return; } // Get our bonded devices. for (BluetoothDevice device : btAdapter.getBondedDevices()) { Log.d(TAG, "Bluetooth device available: " + device); if (isSteamController(device)) { connectBluetoothDevice(device); } } // NOTE: These don't work on Chromebooks, to my undying dismay. IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); mContext.registerReceiver(mBluetoothBroadcast, filter); if (mIsChromebook) { mHandler = new Handler(Looper.getMainLooper()); mLastBluetoothDevices = new ArrayList(); // final HIDDeviceManager finalThis = this; // mHandler.postDelayed(new Runnable() { // @Override // public void run() { // finalThis.chromebookConnectionHandler(); // } // }, 5000); } } private void shutdownBluetooth() { try { mContext.unregisterReceiver(mBluetoothBroadcast); } catch (Exception e) { // We may not have registered, that's okay } } // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. // This function provides a sort of dummy version of that, watching for changes in the // connected devices and attempting to add controllers as things change. public void chromebookConnectionHandler() { if (!mIsChromebook) { return; } ArrayList disconnected = new ArrayList(); ArrayList connected = new ArrayList(); List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); for (BluetoothDevice bluetoothDevice : currentConnected) { if (!mLastBluetoothDevices.contains(bluetoothDevice)) { connected.add(bluetoothDevice); } } for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { if (!currentConnected.contains(bluetoothDevice)) { disconnected.add(bluetoothDevice); } } mLastBluetoothDevices = currentConnected; for (BluetoothDevice bluetoothDevice : disconnected) { disconnectBluetoothDevice(bluetoothDevice); } for (BluetoothDevice bluetoothDevice : connected) { connectBluetoothDevice(bluetoothDevice); } final HIDDeviceManager finalThis = this; mHandler.postDelayed(new Runnable() { @Override public void run() { finalThis.chromebookConnectionHandler(); } }, 10000); } public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); synchronized (this) { if (mBluetoothDevices.containsKey(bluetoothDevice)) { Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); device.reconnect(); return false; } HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); int id = device.getId(); mBluetoothDevices.put(bluetoothDevice, device); mDevicesById.put(id, device); // The Steam Controller will mark itself connected once initialization is complete } return true; } public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { synchronized (this) { HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); if (device == null) return; int id = device.getId(); mBluetoothDevices.remove(bluetoothDevice); mDevicesById.remove(id); device.shutdown(); HIDDeviceDisconnected(id); } } public boolean isSteamController(BluetoothDevice bluetoothDevice) { // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. if (bluetoothDevice == null) { return false; } // If the device has no local name, we really don't want to try an equality check against it. if (bluetoothDevice.getName() == null) { return false; } return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0); } private void close() { shutdownUSB(); shutdownBluetooth(); synchronized (this) { for (HIDDevice device : mDevicesById.values()) { device.shutdown(); } mDevicesById.clear(); mBluetoothDevices.clear(); HIDDeviceReleaseCallback(); } } public void setFrozen(boolean frozen) { synchronized (this) { for (HIDDevice device : mDevicesById.values()) { device.setFrozen(frozen); } } } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// private HIDDevice getDevice(int id) { synchronized (this) { HIDDevice result = mDevicesById.get(id); if (result == null) { Log.v(TAG, "No device for id: " + id); Log.v(TAG, "Available devices: " + mDevicesById.keySet()); } return result; } } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////// JNI interface functions ////////////////////////////////////////////////////////////////////////////////////////////////////// public boolean initialize(boolean usb, boolean bluetooth) { Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); if (usb) { initializeUSB(); } if (bluetooth) { initializeBluetooth(); } return true; } public boolean openDevice(int deviceID) { Log.v(TAG, "openDevice deviceID=" + deviceID); HIDDevice device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return false; } // Look to see if this is a USB device and we have permission to access it UsbDevice usbDevice = device.getDevice(); if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { HIDDeviceOpenPending(deviceID); try { final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 int flags; if (Build.VERSION.SDK_INT >= 31) { flags = FLAG_MUTABLE; } else { flags = 0; } mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); } catch (Exception e) { Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); HIDDeviceOpenResult(deviceID, false); } return false; } try { return device.open(); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return false; } public int sendOutputReport(int deviceID, byte[] report) { try { //Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return -1; } return device.sendOutputReport(report); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return -1; } public int sendFeatureReport(int deviceID, byte[] report) { try { //Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return -1; } return device.sendFeatureReport(report); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return -1; } public boolean getFeatureReport(int deviceID, byte[] report) { try { //Log.v(TAG, "getFeatureReport deviceID=" + deviceID); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return false; } return device.getFeatureReport(report); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return false; } public void closeDevice(int deviceID) { try { Log.v(TAG, "closeDevice deviceID=" + deviceID); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return; } device.close(); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } } ////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////// Native methods ////////////////////////////////////////////////////////////////////////////////////////////////////// private native void HIDDeviceRegisterCallback(); private native void HIDDeviceReleaseCallback(); native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol); native void HIDDeviceOpenPending(int deviceID); native void HIDDeviceOpenResult(int deviceID, boolean opened); native void HIDDeviceDisconnected(int deviceID); native void HIDDeviceInputReport(int deviceID, byte[] report); native void HIDDeviceFeatureReport(int deviceID, byte[] report); } flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java000066400000000000000000000214711434514645000311560ustar00rootroot00000000000000package org.libsdl.app; import android.hardware.usb.*; import android.os.Build; import android.util.Log; import java.util.Arrays; class HIDDeviceUSB implements HIDDevice { private static final String TAG = "hidapi"; protected HIDDeviceManager mManager; protected UsbDevice mDevice; protected int mInterfaceIndex; protected int mInterface; protected int mDeviceId; protected UsbDeviceConnection mConnection; protected UsbEndpoint mInputEndpoint; protected UsbEndpoint mOutputEndpoint; protected InputThread mInputThread; protected boolean mRunning; protected boolean mFrozen; public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { mManager = manager; mDevice = usbDevice; mInterfaceIndex = interface_index; mInterface = mDevice.getInterface(mInterfaceIndex).getId(); mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); mRunning = false; } public String getIdentifier() { return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); } @Override public int getId() { return mDeviceId; } @Override public int getVendorId() { return mDevice.getVendorId(); } @Override public int getProductId() { return mDevice.getProductId(); } @Override public String getSerialNumber() { String result = null; if (Build.VERSION.SDK_INT >= 21) { try { result = mDevice.getSerialNumber(); } catch (SecurityException exception) { //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); } } if (result == null) { result = ""; } return result; } @Override public int getVersion() { return 0; } @Override public String getManufacturerName() { String result = null; if (Build.VERSION.SDK_INT >= 21) { result = mDevice.getManufacturerName(); } if (result == null) { result = String.format("%x", getVendorId()); } return result; } @Override public String getProductName() { String result = null; if (Build.VERSION.SDK_INT >= 21) { result = mDevice.getProductName(); } if (result == null) { result = String.format("%x", getProductId()); } return result; } @Override public UsbDevice getDevice() { return mDevice; } public String getDeviceName() { return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; } @Override public boolean open() { mConnection = mManager.getUSBManager().openDevice(mDevice); if (mConnection == null) { Log.w(TAG, "Unable to open USB device " + getDeviceName()); return false; } // Force claim our interface UsbInterface iface = mDevice.getInterface(mInterfaceIndex); if (!mConnection.claimInterface(iface, true)) { Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); close(); return false; } // Find the endpoints for (int j = 0; j < iface.getEndpointCount(); j++) { UsbEndpoint endpt = iface.getEndpoint(j); switch (endpt.getDirection()) { case UsbConstants.USB_DIR_IN: if (mInputEndpoint == null) { mInputEndpoint = endpt; } break; case UsbConstants.USB_DIR_OUT: if (mOutputEndpoint == null) { mOutputEndpoint = endpt; } break; } } // Make sure the required endpoints were present if (mInputEndpoint == null || mOutputEndpoint == null) { Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); close(); return false; } // Start listening for input mRunning = true; mInputThread = new InputThread(); mInputThread.start(); return true; } @Override public int sendFeatureReport(byte[] report) { int res = -1; int offset = 0; int length = report.length; boolean skipped_report_id = false; byte report_number = report[0]; if (report_number == 0x0) { ++offset; --length; skipped_report_id = true; } res = mConnection.controlTransfer( UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, 0x09/*HID set_report*/, (3/*HID feature*/ << 8) | report_number, mInterface, report, offset, length, 1000/*timeout millis*/); if (res < 0) { Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName()); return -1; } if (skipped_report_id) { ++length; } return length; } @Override public int sendOutputReport(byte[] report) { int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); if (r != report.length) { Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName()); } return r; } @Override public boolean getFeatureReport(byte[] report) { int res = -1; int offset = 0; int length = report.length; boolean skipped_report_id = false; byte report_number = report[0]; if (report_number == 0x0) { /* Offset the return buffer by 1, so that the report ID will remain in byte 0. */ ++offset; --length; skipped_report_id = true; } res = mConnection.controlTransfer( UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, 0x01/*HID get_report*/, (3/*HID feature*/ << 8) | report_number, mInterface, report, offset, length, 1000/*timeout millis*/); if (res < 0) { Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); return false; } if (skipped_report_id) { ++res; ++length; } byte[] data; if (res == length) { data = report; } else { data = Arrays.copyOfRange(report, 0, res); } mManager.HIDDeviceFeatureReport(mDeviceId, data); return true; } @Override public void close() { mRunning = false; if (mInputThread != null) { while (mInputThread.isAlive()) { mInputThread.interrupt(); try { mInputThread.join(); } catch (InterruptedException e) { // Keep trying until we're done } } mInputThread = null; } if (mConnection != null) { UsbInterface iface = mDevice.getInterface(mInterfaceIndex); mConnection.releaseInterface(iface); mConnection.close(); mConnection = null; } } @Override public void shutdown() { close(); mManager = null; } @Override public void setFrozen(boolean frozen) { mFrozen = frozen; } protected class InputThread extends Thread { @Override public void run() { int packetSize = mInputEndpoint.getMaxPacketSize(); byte[] packet = new byte[packetSize]; while (mRunning) { int r; try { r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); } catch (Exception e) { Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); break; } if (r < 0) { // Could be a timeout or an I/O error } if (r > 0) { byte[] data; if (r == packetSize) { data = packet; } else { data = Arrays.copyOfRange(packet, 0, r); } if (!mFrozen) { mManager.HIDDeviceInputReport(mDeviceId, data); } } } } } } flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/app/SDL.java000066400000000000000000000062711434514645000275030ustar00rootroot00000000000000package org.libsdl.app; import android.content.Context; import java.lang.Class; import java.lang.reflect.Method; /** SDL library initialization */ public class SDL { // This function should be called first and sets up the native code // so it can call into the Java classes public static void setupJNI() { SDLActivity.nativeSetupJNI(); SDLAudioManager.nativeSetupJNI(); SDLControllerManager.nativeSetupJNI(); } // This function should be called each time the activity is started public static void initialize() { setContext(null); SDLActivity.initialize(); SDLAudioManager.initialize(); SDLControllerManager.initialize(); } // This function stores the current activity (SDL or not) public static void setContext(Context context) { mContext = context; } public static Context getContext() { return mContext; } public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { if (libraryName == null) { throw new NullPointerException("No library name provided."); } try { // Let's see if we have ReLinker available in the project. This is necessary for // some projects that have huge numbers of local libraries bundled, and thus may // trip a bug in Android's native library loader which ReLinker works around. (If // loadLibrary works properly, ReLinker will simply use the normal Android method // internally.) // // To use ReLinker, just add it as a dependency. For more information, see // https://github.com/KeepSafe/ReLinker for ReLinker's repository. // Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if // they've changed during updates. Method forceMethod = relinkClass.getDeclaredMethod("force"); Object relinkInstance = forceMethod.invoke(null); Class relinkInstanceClass = relinkInstance.getClass(); // Actually load the library! Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); } catch (final Throwable e) { // Fall back try { System.loadLibrary(libraryName); } catch (final UnsatisfiedLinkError ule) { throw ule; } catch (final SecurityException se) { throw se; } } } protected static Context mContext; } flare-engine-1.14/flare-android-project/app/src/main/java/org/libsdl/app/SDLActivity.java000066400000000000000000002231431434514645000312170ustar00rootroot00000000000000package org.libsdl.app; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.UiModeManager; import android.content.ClipboardManager; import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.hardware.Sensor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.Editable; import android.text.InputType; import android.text.Selection; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.view.Display; import android.view.Gravity; import android.view.InputDevice; import android.view.KeyEvent; import android.view.PointerIcon; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import java.util.Hashtable; import java.util.Locale; /** SDL Activity */ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; private static final int SDL_MINOR_VERSION = 25; private static final int SDL_MICRO_VERSION = 0; /* // Display InputType.SOURCE/CLASS of events and devices // // SDLActivity.debugSource(device.getSources(), "device[" + device.getName() + "]"); // SDLActivity.debugSource(event.getSource(), "event"); public static void debugSource(int sources, String prefix) { int s = sources; int s_copy = sources; String cls = ""; String src = ""; int tst = 0; int FLAG_TAINTED = 0x80000000; if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0) cls += " BUTTON"; if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) cls += " JOYSTICK"; if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0) cls += " POINTER"; if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0) cls += " POSITION"; if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) cls += " TRACKBALL"; int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits s2 &= ~( InputDevice.SOURCE_CLASS_BUTTON | InputDevice.SOURCE_CLASS_JOYSTICK | InputDevice.SOURCE_CLASS_POINTER | InputDevice.SOURCE_CLASS_POSITION | InputDevice.SOURCE_CLASS_TRACKBALL); if (s2 != 0) cls += "Some_Unkown"; s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; s2 &= ~tst; } tst = InputDevice.SOURCE_DPAD; if ((s & tst) == tst) src += " DPAD"; s2 &= ~tst; tst = InputDevice.SOURCE_GAMEPAD; if ((s & tst) == tst) src += " GAMEPAD"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { tst = InputDevice.SOURCE_HDMI; if ((s & tst) == tst) src += " HDMI"; s2 &= ~tst; } tst = InputDevice.SOURCE_JOYSTICK; if ((s & tst) == tst) src += " JOYSTICK"; s2 &= ~tst; tst = InputDevice.SOURCE_KEYBOARD; if ((s & tst) == tst) src += " KEYBOARD"; s2 &= ~tst; tst = InputDevice.SOURCE_MOUSE; if ((s & tst) == tst) src += " MOUSE"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= 26) { tst = InputDevice.SOURCE_MOUSE_RELATIVE; if ((s & tst) == tst) src += " MOUSE_RELATIVE"; s2 &= ~tst; tst = InputDevice.SOURCE_ROTARY_ENCODER; if ((s & tst) == tst) src += " ROTARY_ENCODER"; s2 &= ~tst; } tst = InputDevice.SOURCE_STYLUS; if ((s & tst) == tst) src += " STYLUS"; s2 &= ~tst; tst = InputDevice.SOURCE_TOUCHPAD; if ((s & tst) == tst) src += " TOUCHPAD"; s2 &= ~tst; tst = InputDevice.SOURCE_TOUCHSCREEN; if ((s & tst) == tst) src += " TOUCHSCREEN"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { tst = InputDevice.SOURCE_TOUCH_NAVIGATION; if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; s2 &= ~tst; } tst = InputDevice.SOURCE_TRACKBALL; if ((s & tst) == tst) src += " TRACKBALL"; s2 &= ~tst; tst = InputDevice.SOURCE_ANY; if ((s & tst) == tst) src += " ANY"; s2 &= ~tst; if (s == FLAG_TAINTED) src += " FLAG_TAINTED"; s2 &= ~FLAG_TAINTED; if (s2 != 0) src += " Some_Unkown"; Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src); } */ public static boolean mIsResumedCalled, mHasFocus; public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); // Cursor types // private static final int SDL_SYSTEM_CURSOR_NONE = -1; private static final int SDL_SYSTEM_CURSOR_ARROW = 0; private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; private static final int SDL_SYSTEM_CURSOR_WAIT = 2; private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; private static final int SDL_SYSTEM_CURSOR_NO = 10; private static final int SDL_SYSTEM_CURSOR_HAND = 11; protected static final int SDL_ORIENTATION_UNKNOWN = 0; protected static final int SDL_ORIENTATION_LANDSCAPE = 1; protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; protected static final int SDL_ORIENTATION_PORTRAIT = 3; protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; protected static int mCurrentOrientation; protected static Locale mCurrentLocale; // Handle the state of the native layer public enum NativeState { INIT, RESUMED, PAUSED } public static NativeState mNextNativeState; public static NativeState mCurrentNativeState; /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ public static boolean mBrokenLibraries = true; // Main components protected static SDLActivity mSingleton; protected static SDLSurface mSurface; protected static DummyEdit mTextEdit; protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; protected static SDLClipboardHandler mClipboardHandler; protected static Hashtable mCursors; protected static int mLastCursorID; protected static SDLGenericMotionListener_API12 mMotionListener; protected static HIDDeviceManager mHIDDeviceManager; // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; protected static SDLGenericMotionListener_API12 getMotionListener() { if (mMotionListener == null) { if (Build.VERSION.SDK_INT >= 26) { mMotionListener = new SDLGenericMotionListener_API26(); } else if (Build.VERSION.SDK_INT >= 24) { mMotionListener = new SDLGenericMotionListener_API24(); } else { mMotionListener = new SDLGenericMotionListener_API12(); } } return mMotionListener; } /** * This method returns the name of the shared object with the application entry point * It can be overridden by derived classes. */ protected String getMainSharedObject() { String library; String[] libraries = SDLActivity.mSingleton.getLibraries(); if (libraries.length > 0) { library = "lib" + libraries[libraries.length - 1] + ".so"; } else { library = "libmain.so"; } return getContext().getApplicationInfo().nativeLibraryDir + "/" + library; } /** * This method returns the name of the application entry point * It can be overridden by derived classes. */ protected String getMainFunction() { return "SDL_main"; } /** * This method is called by SDL before loading the native shared libraries. * It can be overridden to provide names of shared libraries to be loaded. * The default implementation returns the defaults. It never returns null. * An array returned by a new implementation must at least contain "SDL2". * Also keep in mind that the order the libraries are loaded may matter. * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). */ protected String[] getLibraries() { return new String[] { "SDL2", // "SDL2_image", // "SDL2_mixer", // "SDL2_net", // "SDL2_ttf", "main" }; } // Load the .so public void loadLibraries() { for (String lib : getLibraries()) { SDL.loadLibrary(lib); } } /** * This method is called by SDL before starting the native application thread. * It can be overridden to provide the arguments after the application name. * The default implementation returns an empty array. It never returns null. * @return arguments for the native application. */ protected String[] getArguments() { return new String[0]; } public static void initialize() { // The static nature of the singleton and Android quirkyness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values mSingleton = null; mSurface = null; mTextEdit = null; mLayout = null; mClipboardHandler = null; mCursors = new Hashtable(); mLastCursorID = 0; mSDLThread = null; mIsResumedCalled = false; mHasFocus = true; mNextNativeState = NativeState.INIT; mCurrentNativeState = NativeState.INIT; } protected SDLSurface createSDLSurface(Context context) { return new SDLSurface(context); } // Setup @Override protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "Device: " + Build.DEVICE); Log.v(TAG, "Model: " + Build.MODEL); Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); try { Thread.currentThread().setName("SDLActivity"); } catch (Exception e) { Log.v(TAG, "modify thread properties failed " + e.toString()); } // Load shared libraries String errorMsgBrokenLib = ""; try { loadLibraries(); mBrokenLibraries = false; /* success */ } catch(UnsatisfiedLinkError e) { System.err.println(e.getMessage()); mBrokenLibraries = true; errorMsgBrokenLib = e.getMessage(); } catch(Exception e) { System.err.println(e.getMessage()); mBrokenLibraries = true; errorMsgBrokenLib = e.getMessage(); } if (!mBrokenLibraries) { String expected_version = String.valueOf(SDL_MAJOR_VERSION) + "." + String.valueOf(SDL_MINOR_VERSION) + "." + String.valueOf(SDL_MICRO_VERSION); String version = nativeGetVersion(); if (!version.equals(expected_version)) { mBrokenLibraries = true; errorMsgBrokenLib = "SDL C/Java version mismatch (expected " + expected_version + ", got " + version + ")"; } } if (mBrokenLibraries) { mSingleton = this; AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + System.getProperty("line.separator") + System.getProperty("line.separator") + "Error: " + errorMsgBrokenLib); dlgAlert.setTitle("SDL Error"); dlgAlert.setPositiveButton("Exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog,int id) { // if this button is clicked, close current activity SDLActivity.mSingleton.finish(); } }); dlgAlert.setCancelable(false); dlgAlert.create().show(); return; } // Set up JNI SDL.setupJNI(); // Initialize state SDL.initialize(); // So we can call stuff from static callbacks mSingleton = this; SDL.setContext(this); mClipboardHandler = new SDLClipboardHandler(); mHIDDeviceManager = HIDDeviceManager.acquire(this); // Set up the surface mSurface = createSDLSurface(getApplication()); mLayout = new RelativeLayout(this); mLayout.addView(mSurface); // Get our current screen orientation and pass it down. mCurrentOrientation = SDLActivity.getCurrentOrientation(); // Only record current orientation SDLActivity.onNativeOrientationChanged(mCurrentOrientation); try { if (Build.VERSION.SDK_INT < 24) { mCurrentLocale = getContext().getResources().getConfiguration().locale; } else { mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); } } catch(Exception ignored) { } setContentView(mLayout); setWindowStyle(false); getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); // Get filename from "Open with" of another application Intent intent = getIntent(); if (intent != null && intent.getData() != null) { String filename = intent.getData().getPath(); if (filename != null) { Log.v(TAG, "Got filename: " + filename); SDLActivity.onNativeDropFile(filename); } } } protected void pauseNativeThread() { mNextNativeState = NativeState.PAUSED; mIsResumedCalled = false; if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.handleNativeState(); } protected void resumeNativeThread() { mNextNativeState = NativeState.RESUMED; mIsResumedCalled = true; if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.handleNativeState(); } // Events @Override protected void onPause() { Log.v(TAG, "onPause()"); super.onPause(); if (mHIDDeviceManager != null) { mHIDDeviceManager.setFrozen(true); } if (!mHasMultiWindow) { pauseNativeThread(); } } @Override protected void onResume() { Log.v(TAG, "onResume()"); super.onResume(); if (mHIDDeviceManager != null) { mHIDDeviceManager.setFrozen(false); } if (!mHasMultiWindow) { resumeNativeThread(); } } @Override protected void onStop() { Log.v(TAG, "onStop()"); super.onStop(); if (mHasMultiWindow) { pauseNativeThread(); } } @Override protected void onStart() { Log.v(TAG, "onStart()"); super.onStart(); if (mHasMultiWindow) { resumeNativeThread(); } } public static int getCurrentOrientation() { int result = SDL_ORIENTATION_UNKNOWN; Activity activity = (Activity)getContext(); if (activity == null) { return result; } Display display = activity.getWindowManager().getDefaultDisplay(); switch (display.getRotation()) { case Surface.ROTATION_0: result = SDL_ORIENTATION_PORTRAIT; break; case Surface.ROTATION_90: result = SDL_ORIENTATION_LANDSCAPE; break; case Surface.ROTATION_180: result = SDL_ORIENTATION_PORTRAIT_FLIPPED; break; case Surface.ROTATION_270: result = SDL_ORIENTATION_LANDSCAPE_FLIPPED; break; } return result; } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); if (SDLActivity.mBrokenLibraries) { return; } mHasFocus = hasFocus; if (hasFocus) { mNextNativeState = NativeState.RESUMED; SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); SDLActivity.handleNativeState(); nativeFocusChanged(true); } else { nativeFocusChanged(false); if (!mHasMultiWindow) { mNextNativeState = NativeState.PAUSED; SDLActivity.handleNativeState(); } } } @Override public void onLowMemory() { Log.v(TAG, "onLowMemory()"); super.onLowMemory(); if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.nativeLowMemory(); } @Override public void onConfigurationChanged(Configuration newConfig) { Log.v(TAG, "onConfigurationChanged()"); super.onConfigurationChanged(newConfig); if (SDLActivity.mBrokenLibraries) { return; } if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { mCurrentLocale = newConfig.locale; SDLActivity.onNativeLocaleChanged(); } } @Override protected void onDestroy() { Log.v(TAG, "onDestroy()"); if (mHIDDeviceManager != null) { HIDDeviceManager.release(mHIDDeviceManager); mHIDDeviceManager = null; } if (SDLActivity.mBrokenLibraries) { super.onDestroy(); return; } if (SDLActivity.mSDLThread != null) { // Send Quit event to "SDLThread" thread SDLActivity.nativeSendQuit(); // Wait for "SDLThread" thread to end try { SDLActivity.mSDLThread.join(); } catch(Exception e) { Log.v(TAG, "Problem stopping SDLThread: " + e); } } SDLActivity.nativeQuit(); super.onDestroy(); } @Override public void onBackPressed() { // Check if we want to block the back button in case of mouse right click. // // If we do, the normal hardware back button will no longer work and people have to use home, // but the mouse right click will work. // boolean trapBack = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_TRAP_BACK_BUTTON", false); if (trapBack) { // Exit and let the mouse handler handle this button (if appropriate) return; } // Default system back button behavior. if (!isFinishing()) { super.onBackPressed(); } } // Called by JNI from SDL. public static void manualBackButton() { mSingleton.pressBackButton(); } // Used to get us onto the activity's main thread public void pressBackButton() { runOnUiThread(new Runnable() { @Override public void run() { if (!SDLActivity.this.isFinishing()) { SDLActivity.this.superOnBackPressed(); } } }); } // Used to access the system back behavior. public void superOnBackPressed() { super.onBackPressed(); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (SDLActivity.mBrokenLibraries) { return false; } int keyCode = event.getKeyCode(); // Ignore certain special keys so they're handled by Android if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ ) { return false; } return super.dispatchKeyEvent(event); } /* Transition to next state */ public static void handleNativeState() { if (mNextNativeState == mCurrentNativeState) { // Already in same state, discard. return; } // Try a transition to init state if (mNextNativeState == NativeState.INIT) { mCurrentNativeState = mNextNativeState; return; } // Try a transition to paused state if (mNextNativeState == NativeState.PAUSED) { if (mSDLThread != null) { nativePause(); } if (mSurface != null) { mSurface.handlePause(); } mCurrentNativeState = mNextNativeState; return; } // Try a transition to resumed state if (mNextNativeState == NativeState.RESUMED) { if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) { if (mSDLThread == null) { // This is the entry point to the C app. // Start up the C app thread and enable sensor input for the first time // FIXME: Why aren't we enabling sensor input at start? mSDLThread = new Thread(new SDLMain(), "SDLThread"); mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); mSDLThread.start(); // No nativeResume(), don't signal Android_ResumeSem } else { nativeResume(); } mSurface.handleResume(); mCurrentNativeState = mNextNativeState; } } } // Messages from the SDLMain thread static final int COMMAND_CHANGE_TITLE = 1; static final int COMMAND_CHANGE_WINDOW_STYLE = 2; static final int COMMAND_TEXTEDIT_HIDE = 3; static final int COMMAND_SET_KEEP_SCREEN_ON = 5; protected static final int COMMAND_USER = 0x8000; protected static boolean mFullscreenModeActive; /** * This method is called by SDL if SDL did not handle a message itself. * This happens if a received message contains an unsupported command. * Method can be overwritten to handle Messages in a different class. * @param command the command of the message. * @param param the parameter of the message. May be null. * @return if the message was handled in overridden method. */ protected boolean onUnhandledMessage(int command, Object param) { return false; } /** * A Handler class for Messages from native SDL applications. * It uses current Activities as target (e.g. for the title). * static to prevent implicit references to enclosing object. */ protected static class SDLCommandHandler extends Handler { @Override public void handleMessage(Message msg) { Context context = SDL.getContext(); if (context == null) { Log.e(TAG, "error handling message, getContext() returned null"); return; } switch (msg.arg1) { case COMMAND_CHANGE_TITLE: if (context instanceof Activity) { ((Activity) context).setTitle((String)msg.obj); } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); } break; case COMMAND_CHANGE_WINDOW_STYLE: if (Build.VERSION.SDK_INT >= 19) { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; window.getDecorView().setSystemUiVisibility(flags); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); SDLActivity.mFullscreenModeActive = true; } else { int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; window.getDecorView().setSystemUiVisibility(flags); window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); SDLActivity.mFullscreenModeActive = false; } } } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); } } break; case COMMAND_TEXTEDIT_HIDE: if (mTextEdit != null) { // Note: On some devices setting view to GONE creates a flicker in landscape. // Setting the View's sizes to 0 is similar to GONE but without the flicker. // The sizes will be set to useful values when the keyboard is shown again. mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); mScreenKeyboardShown = false; mSurface.requestFocus(); } break; case COMMAND_SET_KEEP_SCREEN_ON: { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } } break; } default: if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { Log.e(TAG, "error handling message, command is " + msg.arg1); } } } } // Handler for the messages Handler commandHandler = new SDLCommandHandler(); // Send a message from the SDLMain thread boolean sendCommand(int command, Object data) { Message msg = commandHandler.obtainMessage(); msg.arg1 = command; msg.obj = data; boolean result = commandHandler.sendMessage(msg); if (Build.VERSION.SDK_INT >= 19) { if (command == COMMAND_CHANGE_WINDOW_STYLE) { // Ensure we don't return until the resize has actually happened, // or 500ms have passed. boolean bShouldWait = false; if (data instanceof Integer) { // Let's figure out if we're already laid out fullscreen or not. Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics realMetrics = new DisplayMetrics(); display.getRealMetrics(realMetrics); boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && (realMetrics.heightPixels == mSurface.getHeight())); if ((Integer) data == 1) { // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going // to change size and should wait for surfaceChanged() before we return, so the size // is right back in native code. If we're already laid out fullscreen, though, we're // not going to change size even if we change decor modes, so we shouldn't wait for // surfaceChanged() -- which may not even happen -- and should return immediately. bShouldWait = !bFullscreenLayout; } else { // If we're laid out fullscreen (even if the status bar and nav bar are present), // or are actively in fullscreen, we're going to change size and should wait for // surfaceChanged before we return, so the size is right back in native code. bShouldWait = bFullscreenLayout; } } if (bShouldWait && (SDLActivity.getContext() != null)) { // We'll wait for the surfaceChanged() method, which will notify us // when called. That way, we know our current size is really the // size we need, instead of grabbing a size that's still got // the navigation and/or status bars before they're hidden. // // We'll wait for up to half a second, because some devices // take a surprisingly long time for the surface resize, but // then we'll just give up and return. // synchronized (SDLActivity.getContext()) { try { SDLActivity.getContext().wait(500); } catch (InterruptedException ie) { ie.printStackTrace(); } } } } } return result; } // C functions we call public static native String nativeGetVersion(); public static native int nativeSetupJNI(); public static native int nativeRunMain(String library, String function, Object arguments); public static native void nativeLowMemory(); public static native void nativeSendQuit(); public static native void nativeQuit(); public static native void nativePause(); public static native void nativeResume(); public static native void nativeFocusChanged(boolean hasFocus); public static native void onNativeDropFile(String filename); public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate); public static native void onNativeResize(); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native boolean onNativeSoftReturnKey(); public static native void onNativeKeyboardFocusLost(); public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); public static native void onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p); public static native void onNativeAccel(float x, float y, float z); public static native void onNativeClipboardChanged(); public static native void onNativeSurfaceCreated(); public static native void onNativeSurfaceChanged(); public static native void onNativeSurfaceDestroyed(); public static native String nativeGetHint(String name); public static native boolean nativeGetHintBoolean(String name, boolean default_value); public static native void nativeSetenv(String name, String value); public static native void onNativeOrientationChanged(int orientation); public static native void nativeAddTouch(int touchId, String name); public static native void nativePermissionResult(int requestCode, boolean result); public static native void onNativeLocaleChanged(); /** * This method is called by SDL using JNI. */ public static boolean setActivityTitle(String title) { // Called from SDLMain() thread and can't directly affect the view return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); } /** * This method is called by SDL using JNI. */ public static void setWindowStyle(boolean fullscreen) { // Called from SDLMain() thread and can't directly affect the view mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); } /** * This method is called by SDL using JNI. * This is a static method for JNI convenience, it calls a non-static method * so that is can be overridden */ public static void setOrientation(int w, int h, boolean resizable, String hint) { if (mSingleton != null) { mSingleton.setOrientationBis(w, h, resizable, hint); } } /** * This can be overridden */ public void setOrientationBis(int w, int h, boolean resizable, String hint) { int orientation_landscape = -1; int orientation_portrait = -1; /* If set, hint "explicitly controls which UI orientations are allowed". */ if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; } else if (hint.contains("LandscapeRight")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } else if (hint.contains("LandscapeLeft")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; } if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; } else if (hint.contains("Portrait")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; } else if (hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; } boolean is_landscape_allowed = (orientation_landscape != -1); boolean is_portrait_allowed = (orientation_portrait != -1); int req; /* Requested orientation */ /* No valid hint, nothing is explicitly allowed */ if (!is_portrait_allowed && !is_landscape_allowed) { if (resizable) { /* All orientations are allowed */ req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; } else { /* Fixed window and nothing specified. Get orientation from w/h of created window */ req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); } } else { /* At least one orientation is allowed */ if (resizable) { if (is_portrait_allowed && is_landscape_allowed) { /* hint allows both landscape and portrait, promote to full sensor */ req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); } } else { /* Fixed window and both orientations are allowed. Choose one. */ if (is_portrait_allowed && is_landscape_allowed) { req = (w > h ? orientation_landscape : orientation_portrait); } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); } } } Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); mSingleton.setRequestedOrientation(req); } /** * This method is called by SDL using JNI. */ public static void minimizeWindow() { if (mSingleton == null) { return; } Intent startMain = new Intent(Intent.ACTION_MAIN); startMain.addCategory(Intent.CATEGORY_HOME); startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mSingleton.startActivity(startMain); } /** * This method is called by SDL using JNI. */ public static boolean shouldMinimizeOnFocusLoss() { /* if (Build.VERSION.SDK_INT >= 24) { if (mSingleton == null) { return true; } if (mSingleton.isInMultiWindowMode()) { return false; } if (mSingleton.isInPictureInPictureMode()) { return false; } } return true; */ return false; } /** * This method is called by SDL using JNI. */ public static boolean isScreenKeyboardShown() { if (mTextEdit == null) { return false; } if (!mScreenKeyboardShown) { return false; } InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); return imm.isAcceptingText(); } /** * This method is called by SDL using JNI. */ public static boolean supportsRelativeMouse() { // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under // Android 7 APIs, and simply returns no data under Android 8 APIs. // // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, // we should stick to relative mode. // if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) { return false; } return SDLActivity.getMotionListener().supportsRelativeMouse(); } /** * This method is called by SDL using JNI. */ public static boolean setRelativeMouseEnabled(boolean enabled) { if (enabled && !supportsRelativeMouse()) { return false; } return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); } /** * This method is called by SDL using JNI. */ public static boolean sendMessage(int command, int param) { if (mSingleton == null) { return false; } return mSingleton.sendCommand(command, param); } /** * This method is called by SDL using JNI. */ public static Context getContext() { return SDL.getContext(); } /** * This method is called by SDL using JNI. */ public static boolean isAndroidTV() { UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { return true; } if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { return true; } if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { return true; } return Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV"); } public static double getDiagonal() { DisplayMetrics metrics = new DisplayMetrics(); Activity activity = (Activity)getContext(); if (activity == null) { return 0.0; } activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); } /** * This method is called by SDL using JNI. */ public static boolean isTablet() { // If our diagonal size is seven inches or greater, we consider ourselves a tablet. return (getDiagonal() >= 7.0); } /** * This method is called by SDL using JNI. */ public static boolean isChromebook() { if (getContext() == null) { return false; } return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); } /** * This method is called by SDL using JNI. */ public static boolean isDeXMode() { if (Build.VERSION.SDK_INT < 24) { return false; } try { final Configuration config = getContext().getResources().getConfiguration(); final Class configClass = config.getClass(); return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) == configClass.getField("semDesktopModeEnabled").getInt(config); } catch(Exception ignored) { return false; } } /** * This method is called by SDL using JNI. */ public static DisplayMetrics getDisplayDPI() { return getContext().getResources().getDisplayMetrics(); } /** * This method is called by SDL using JNI. */ public static boolean getManifestEnvironmentVariables() { try { if (getContext() == null) { return false; } ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = applicationInfo.metaData; if (bundle == null) { return false; } String prefix = "SDL_ENV."; final int trimLength = prefix.length(); for (String key : bundle.keySet()) { if (key.startsWith(prefix)) { String name = key.substring(trimLength); String value = bundle.get(key).toString(); nativeSetenv(name, value); } } /* environment variables set! */ return true; } catch (Exception e) { Log.v(TAG, "exception " + e.toString()); } return false; } // This method is called by SDLControllerManager's API 26 Generic Motion Handler. public static View getContentView() { return mLayout; } static class ShowTextInputTask implements Runnable { /* * This is used to regulate the pan&scan method to have some offset from * the bottom edge of the input region and the top edge of an input * method (soft keyboard) */ static final int HEIGHT_PADDING = 15; public int x, y, w, h; public ShowTextInputTask(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; /* Minimum size of 1 pixel, so it takes focus. */ if (this.w <= 0) { this.w = 1; } if (this.h + HEIGHT_PADDING <= 0) { this.h = 1 - HEIGHT_PADDING; } } @Override public void run() { RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); params.leftMargin = x; params.topMargin = y; if (mTextEdit == null) { mTextEdit = new DummyEdit(SDL.getContext()); mLayout.addView(mTextEdit, params); } else { mTextEdit.setLayoutParams(params); } mTextEdit.setVisibility(View.VISIBLE); mTextEdit.requestFocus(); InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mTextEdit, 0); mScreenKeyboardShown = true; } } /** * This method is called by SDL using JNI. */ public static boolean showTextInput(int x, int y, int w, int h) { // Transfer the task to the main thread as a Runnable return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); } public static boolean isTextInputEvent(KeyEvent event) { // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT if (event.isCtrlPressed()) { return false; } return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; } public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) { int deviceId = event.getDeviceId(); int source = event.getSource(); if (source == InputDevice.SOURCE_UNKNOWN) { InputDevice device = InputDevice.getDevice(deviceId); if (device != null) { source = device.getSources(); } } // if (event.getAction() == KeyEvent.ACTION_DOWN) { // Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); // } else if (event.getAction() == KeyEvent.ACTION_UP) { // Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); // } // Dispatch the different events depending on where they come from // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD // // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and // SOURCE_JOYSTICK, while its key events arrive from the keyboard source // So, retrieve the device itself and check all of its sources if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { // Note that we process events with specific key codes here if (event.getAction() == KeyEvent.ACTION_DOWN) { if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { return true; } } else if (event.getAction() == KeyEvent.ACTION_UP) { if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { return true; } } } if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) { if (event.getAction() == KeyEvent.ACTION_DOWN) { if (isTextInputEvent(event)) { if (ic != null) { ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); } else { SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); } } onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { onNativeKeyUp(keyCode); return true; } } if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses // they are ignored here because sending them as mouse input to SDL is messy if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { switch (event.getAction()) { case KeyEvent.ACTION_DOWN: case KeyEvent.ACTION_UP: // mark the event as handled or it will be handled by system // handling KEYCODE_BACK by system will call onBackPressed() return true; } } } return false; } /** * This method is called by SDL using JNI. */ public static Surface getNativeSurface() { if (SDLActivity.mSurface == null) { return null; } return SDLActivity.mSurface.getNativeSurface(); } // Input /** * This method is called by SDL using JNI. */ public static void initTouch() { int[] ids = InputDevice.getDeviceIds(); for (int id : ids) { InputDevice device = InputDevice.getDevice(id); /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */ if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN || device.isVirtual())) { int touchDevId = device.getId(); /* * Prevent id to be -1, since it's used in SDL internal for synthetic events * Appears when using Android emulator, eg: * adb shell input mouse tap 100 100 * adb shell input touchscreen tap 100 100 */ if (touchDevId < 0) { touchDevId -= 1; } nativeAddTouch(touchDevId, device.getName()); } } } // Messagebox /** Result of current messagebox. Also used for blocking the calling thread. */ protected final int[] messageboxSelection = new int[1]; /** * This method is called by SDL using JNI. * Shows the messagebox from UI thread and block calling thread. * buttonFlags, buttonIds and buttonTexts must have same length. * @param buttonFlags array containing flags for every button. * @param buttonIds array containing id for every button. * @param buttonTexts array containing text for every button. * @param colors null for default or array of length 5 containing colors. * @return button id or -1. */ public int messageboxShowMessageBox( final int flags, final String title, final String message, final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors) { messageboxSelection[0] = -1; // sanity checks if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { return -1; // implementation broken } // collect arguments for Dialog final Bundle args = new Bundle(); args.putInt("flags", flags); args.putString("title", title); args.putString("message", message); args.putIntArray("buttonFlags", buttonFlags); args.putIntArray("buttonIds", buttonIds); args.putStringArray("buttonTexts", buttonTexts); args.putIntArray("colors", colors); // trigger Dialog creation on UI thread runOnUiThread(new Runnable() { @Override public void run() { messageboxCreateAndShow(args); } }); // block the calling thread synchronized (messageboxSelection) { try { messageboxSelection.wait(); } catch (InterruptedException ex) { ex.printStackTrace(); return -1; } } // return selected value return messageboxSelection[0]; } protected void messageboxCreateAndShow(Bundle args) { // TODO set values from "flags" to messagebox dialog // get colors int[] colors = args.getIntArray("colors"); int backgroundColor; int textColor; int buttonBorderColor; int buttonBackgroundColor; int buttonSelectedColor; if (colors != null) { int i = -1; backgroundColor = colors[++i]; textColor = colors[++i]; buttonBorderColor = colors[++i]; buttonBackgroundColor = colors[++i]; buttonSelectedColor = colors[++i]; } else { backgroundColor = Color.TRANSPARENT; textColor = Color.TRANSPARENT; buttonBorderColor = Color.TRANSPARENT; buttonBackgroundColor = Color.TRANSPARENT; buttonSelectedColor = Color.TRANSPARENT; } // create dialog with title and a listener to wake up calling thread final AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(args.getString("title")); dialog.setCancelable(false); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface unused) { synchronized (messageboxSelection) { messageboxSelection.notify(); } } }); // create text TextView message = new TextView(this); message.setGravity(Gravity.CENTER); message.setText(args.getString("message")); if (textColor != Color.TRANSPARENT) { message.setTextColor(textColor); } // create buttons int[] buttonFlags = args.getIntArray("buttonFlags"); int[] buttonIds = args.getIntArray("buttonIds"); String[] buttonTexts = args.getStringArray("buttonTexts"); final SparseArray