From f4898539e9c99f0f2c4a36c0f47bf0f90dcc0290 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 13:06:01 +0100 Subject: [PATCH 01/85] added some code that doesn't do anything yet, waiting for ogre 1.8 --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/occlusionquery.cpp | 27 +++++++++++++++++++++++ apps/openmw/mwrender/occlusionquery.hpp | 27 +++++++++++++++++++++++ apps/openmw/mwrender/renderingmanager.cpp | 3 +++ apps/openmw/mwrender/renderingmanager.hpp | 7 ++++-- 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwrender/occlusionquery.cpp create mode 100644 apps/openmw/mwrender/occlusionquery.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2630098f5c..7d4836d3b1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap + renderinginterface localmap occlusionquery ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp new file mode 100644 index 0000000000..395660bb5c --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -0,0 +1,27 @@ +#include "occlusionquery.hpp" + +#include +#include + +using namespace MWRender; +using namespace Ogre; + +OcclusionQuery::OcclusionQuery() : + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0) +{ + try { + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + + mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery(); + + mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0); + } + catch (Ogre::Exception e) + { + mSupported = false; + } + + if (!mSupported) + std::cout << "Hardware occlusion queries not supported." << std::endl; +} diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp new file mode 100644 index 0000000000..619b072ee0 --- /dev/null +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -0,0 +1,27 @@ +#ifndef _GAME_OCCLUSION_QUERY_H +#define _GAME_OCCLUSION_QUERY_H + +#include + +namespace MWRender +{ + /// + /// \brief Implements hardware occlusion queries on the GPU + /// + class OcclusionQuery + { + public: + OcclusionQuery(); + + bool supported(); + ///< returns true if occlusion queries are supported on the user's hardware + + private: + Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; + Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + + bool mSupported; + }; +} + +#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e2aea19c6e..78287dadb5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -49,6 +49,8 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const Ogre::SceneNode *cameraYawNode = playerNode->createChildSceneNode(); Ogre::SceneNode *cameraPitchNode = cameraYawNode->createChildSceneNode(); cameraPitchNode->attachObject(mRendering.getCamera()); + + mOcclusionQuery = new OcclusionQuery(); //mSkyManager = 0; mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera(), &environment); @@ -65,6 +67,7 @@ RenderingManager::~RenderingManager () delete mPlayer; delete mSkyManager; delete mLocalMap; + delete mOcclusionQuery; } MWRender::SkyManager* RenderingManager::getSkyManager() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 78a1d2fdb7..996396633c 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -25,6 +25,7 @@ #include "actors.hpp" #include "player.hpp" #include "localmap.hpp" +#include "occlusionquery.hpp" namespace Ogre { @@ -131,9 +132,11 @@ class RenderingManager: private RenderingInterface { private: void setAmbientMode(); - + SkyManager* mSkyManager; - + + OcclusionQuery* mOcclusionQuery; + OEngine::Render::OgreRenderer &mRendering; MWRender::Objects mObjects; From 743ea0c9be16a6864bf861db101dd5901452045a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 17:59:26 +0100 Subject: [PATCH 02/85] use hardware occlusion query for sun glare effect --- apps/openmw/mwrender/occlusionquery.cpp | 116 +++++++++++++++++++++- apps/openmw/mwrender/occlusionquery.hpp | 28 +++++- apps/openmw/mwrender/renderingmanager.cpp | 12 ++- apps/openmw/mwrender/renderingmanager.hpp | 4 +- apps/openmw/mwrender/sky.cpp | 52 ++++++---- apps/openmw/mwrender/sky.hpp | 48 ++++----- apps/openmw/mwworld/world.cpp | 17 ++-- 7 files changed, 216 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 395660bb5c..6c74561e95 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -2,13 +2,17 @@ #include #include +#include using namespace MWRender; using namespace Ogre; -OcclusionQuery::OcclusionQuery() : - mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0) +OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0) { + mRendering = renderer; + mSunNode = sunNode; + try { RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); @@ -23,5 +27,113 @@ OcclusionQuery::OcclusionQuery() : } if (!mSupported) + { std::cout << "Hardware occlusion queries not supported." << std::endl; + return; + } + + // This means that everything up to RENDER_QUEUE_MAIN can occlude the objects that are tested + const int queue = RENDER_QUEUE_MAIN+1; + + MaterialPtr matBase = MaterialManager::getSingleton().getByName("BaseWhiteNoLighting"); + MaterialPtr matQueryArea = matBase->clone("QueryTotalPixels"); + matQueryArea->setDepthWriteEnabled(false); + matQueryArea->setColourWriteEnabled(false); + matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects + MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels"); + matQueryVisible->setDepthWriteEnabled(false); + matQueryVisible->setColourWriteEnabled(false); + matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); + mBBQueryTotal->setDefaultDimensions(150, 150); + mBBQueryTotal->createBillboard(Vector3::ZERO); + mBBQueryTotal->setMaterialName("QueryTotalPixels"); + mBBQueryTotal->setRenderQueueGroup(queue); + mSunNode->attachObject(mBBQueryTotal); + + mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); + mBBQueryVisible->setDefaultDimensions(150, 150); + mBBQueryVisible->createBillboard(Vector3::ZERO); + mBBQueryVisible->setMaterialName("QueryVisiblePixels"); + mBBQueryVisible->setRenderQueueGroup(queue); + mSunNode->attachObject(mBBQueryVisible); + + mRendering->getScene()->addRenderObjectListener(this); + mDoQuery = true; } + +OcclusionQuery::~OcclusionQuery() +{ + RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); + if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); + if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); +} + +bool OcclusionQuery::supported() +{ + return mSupported; +} + +void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, + const LightList* pLightList, bool suppressRenderStateChanges) +{ + if (!mSupported) return; + + // The following code activates and deactivates the occlusion queries + // so that the queries only include the rendering of their intended targets + + // Close the last occlusion query + // Each occlusion query should only last a single rendering + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } + + // Open a new occlusion query + if (mDoQuery == true) + { + if (rend == mBBQueryTotal) + mActiveQuery = mSunTotalAreaQuery; + else if (rend == mBBQueryVisible) + mActiveQuery = mSunVisibleAreaQuery; + + if (mActiveQuery != NULL) + { + mActiveQuery->beginOcclusionQuery(); + } + } +} + +void OcclusionQuery::update() +{ + if (!mSupported) return; + + // Stop occlusion queries until we get their information + // (may not happen on the same frame they are requested in) + mDoQuery = false; + + if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) + { + unsigned int totalPixels; + unsigned int visiblePixels; + + mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels); + mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels); + + if (totalPixels == 0) + { + // probably outside of the view frustum + mSunVisibility = 0; + } + else + { + mSunVisibility = float(visiblePixels) / float(totalPixels); + if (mSunVisibility > 1) mSunVisibility = 1; + } + + mDoQuery = true; + } +} + diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 619b072ee0..e6adc0d4b3 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -2,25 +2,49 @@ #define _GAME_OCCLUSION_QUERY_H #include +#include + +#include namespace MWRender { /// /// \brief Implements hardware occlusion queries on the GPU /// - class OcclusionQuery + class OcclusionQuery : public Ogre::RenderObjectListener { public: - OcclusionQuery(); + OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); + ~OcclusionQuery(); bool supported(); ///< returns true if occlusion queries are supported on the user's hardware + void update(); + ///< per-frame update + + float getSunVisibility() const {return mSunVisibility;}; + private: Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + Ogre::HardwareOcclusionQuery* mActiveQuery; + + Ogre::BillboardSet* mBBQueryVisible; + Ogre::BillboardSet* mBBQueryTotal; + + Ogre::SceneNode* mSunNode; + + float mSunVisibility; bool mSupported; + bool mDoQuery; + + OEngine::Render::OgreRenderer* mRendering; + + protected: + virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source, + const Ogre::LightList* pLightList, bool suppressRenderStateChanges); }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7aa623879e..b4711e8df7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -54,12 +54,12 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const Ogre::SceneNode *cameraYawNode = playerNode->createChildSceneNode(); Ogre::SceneNode *cameraPitchNode = cameraYawNode->createChildSceneNode(); cameraPitchNode->attachObject(mRendering.getCamera()); - - mOcclusionQuery = new OcclusionQuery(); //mSkyManager = 0; mSkyManager = new SkyManager(mMwRoot, mRendering.getCamera(), &environment); + mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); + mPlayer = new MWRender::Player (mRendering.getCamera(), playerNode); mSun = 0; @@ -149,9 +149,13 @@ void RenderingManager::moveObjectToCell (const MWWorld::Ptr& ptr, const Ogre::Ve void RenderingManager::update (float duration){ mActors.update (duration); - + + mOcclusionQuery->update(); + mSkyManager->update(duration); - + + mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); + mRendering.update(duration); mLocalMap->updatePlayer( mRendering.getCamera()->getRealPosition(), mRendering.getCamera()->getRealDirection() ); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index acaf8565bb..450e461730 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -98,7 +98,9 @@ class RenderingManager: private RenderingInterface { void setSunDirection(const Ogre::Vector3& direction); void sunEnable(); void sunDisable(); - + + bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }; + void setGlare(bool glare); void skyEnable (); void skyDisable (); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 5e85780022..a70913b239 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -12,6 +12,7 @@ #include "../mwworld/environment.hpp" #include "../mwworld/world.hpp" +#include "occlusionquery.hpp" using namespace MWRender; using namespace Ogre; @@ -30,7 +31,7 @@ BillboardObject::BillboardObject() void BillboardObject::setVisible(const bool visible) { - mNode->setVisible(visible); + mBBSet->setVisible(visible); } void BillboardObject::setSize(const float size) @@ -88,7 +89,7 @@ void BillboardObject::init(const String& textureName, /// \todo These billboards are not 100% correct, might want to revisit them later mBBSet = sceneMgr->createBillboardSet("SkyBillboardSet"+StringConverter::toString(bodyCount), 1); mBBSet->setDefaultDimensions(550.f*initialSize, 550.f*initialSize); - mBBSet->setRenderQueueGroup(RENDER_QUEUE_SKIES_EARLY+2); + mBBSet->setRenderQueueGroup(RENDER_QUEUE_MAIN+2); mBBSet->setBillboardType(BBT_PERPENDICULAR_COMMON); mBBSet->setCommonDirection( -position.normalisedCopy() ); mNode = rootNode->createChildSceneNode(); @@ -293,7 +294,7 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) } SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env) : - mGlareFade(0), mGlareEnabled(false) + mGlare(0), mGlareFade(0) { mEnvironment = env; mViewport = pCamera->getViewport(); @@ -562,10 +563,23 @@ void SkyManager::update(float duration) mMasser->setPhase( static_cast( (int) ((mDay % 32)/4.f)) ); mSecunda->setPhase ( static_cast( (int) ((mDay % 32)/4.f)) ); - // increase the strength of the sun glare effect depending - // on how directly the player is looking at the sun + if (mSunEnabled) { + // take 1/5 sec for fading the glare effect from invisible to full + if (mGlareFade > mGlare) + { + mGlareFade -= duration*5; + if (mGlareFade < mGlare) mGlareFade = mGlare; + } + else if (mGlareFade < mGlare) + { + mGlareFade += duration*5; + if (mGlareFade > mGlare) mGlareFade = mGlare; + } + + // increase the strength of the sun glare effect depending + // on how directly the player is looking at the sun Vector3 sun = mSunGlare->getPosition(); sun = Vector3(sun.x, sun.z, -sun.y); Vector3 cam = mViewport->getCamera()->getRealDirection(); @@ -573,21 +587,10 @@ void SkyManager::update(float duration) float val = 1- (angle.valueDegrees() / 180.f); val = (val*val*val*val)*2; - if (mGlareEnabled) - { - mGlareFade += duration*3; - if (mGlareFade > 1) mGlareFade = 1; - } - else - { - mGlareFade -= duration*3; - if (mGlareFade < 0.3) mGlareFade = 0; - } - - mSunGlare->setSize(val * (mGlareFade)); + mSunGlare->setSize(val * mGlareFade); } - mSunGlare->setVisible(mGlareFade>0 && mSunEnabled); + mSunGlare->setVisible(mSunEnabled); mSun->setVisible(mSunEnabled); mMasser->setVisible(mMasserEnabled); mSecunda->setVisible(mSecundaEnabled); @@ -689,15 +692,15 @@ void SkyManager::setWeather(const MWWorld::WeatherResult& weather) else strength = 1.f; - mSunGlare->setVisibility(weather.mGlareView * strength); - mSun->setVisibility(strength); + mSunGlare->setVisibility(weather.mGlareView * mGlareFade * strength); + mSun->setVisibility(mGlareFade >= 0.5 ? weather.mGlareView * mGlareFade * strength : 0); mAtmosphereNight->setVisible(weather.mNight && mEnabled); } -void SkyManager::setGlare(bool glare) +void SkyManager::setGlare(const float glare) { - mGlareEnabled = glare; + mGlare = glare; } Vector3 SkyManager::getRealSunPos() @@ -782,3 +785,8 @@ void SkyManager::setDate(int day, int month) mDay = day; mMonth = month; } + +Ogre::SceneNode* SkyManager::getSunNode() +{ + return mSun->getNode(); +} diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index bf52afd8dd..508af76732 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -109,58 +109,60 @@ namespace MWRender public: SkyManager(Ogre::SceneNode* pMwRoot, Ogre::Camera* pCamera, MWWorld::Environment* env); ~SkyManager(); - + void update(float duration); - + void enable(); - + void disable(); - + void setHour (double hour); ///< will be called even when sky is disabled. - + void setDate (int day, int month); ///< will be called even when sky is disabled. - + int getMasserPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + int getSecundaPhase() const; ///< 0 new moon, 1 waxing or waning cresecent, 2 waxing or waning half, /// 3 waxing or waning gibbous, 4 full moon - + void setMoonColour (bool red); ///< change Secunda colour to red - + void setCloudsOpacity(float opacity); ///< change opacity of the clouds - + void setWeather(const MWWorld::WeatherResult& weather); - + + Ogre::SceneNode* getSunNode(); + void sunEnable(); - + void sunDisable(); - + void setSunDirection(const Ogre::Vector3& direction); - + void setMasserDirection(const Ogre::Vector3& direction); - + void setSecundaDirection(const Ogre::Vector3& direction); - + void setMasserFade(const float fade); - + void setSecundaFade(const float fade); - + void masserEnable(); void masserDisable(); void secundaEnable(); void secundaDisable(); - + void setThunder(const float factor); - - void setGlare(bool glare); + + void setGlare(const float glare); Ogre::Vector3 getRealSunPos(); private: @@ -203,12 +205,12 @@ namespace MWRender float mRemainingTransitionTime; - float mGlareFade; + float mGlare; // target + float mGlareFade; // actual void ModVertexAlpha(Ogre::Entity* ent, unsigned int meshType); bool mEnabled; - bool mGlareEnabled; bool mSunEnabled; bool mMasserEnabled; bool mSecundaEnabled; diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index a636ce2887..261f66ff4f 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -705,13 +705,16 @@ namespace MWWorld mWeatherManager->update (duration); - // cast a ray from player to sun to detect if the sun is visible - // this is temporary until we find a better place to put this code - // currently its here because we need to access the physics system - float* p = mPlayer->getPlayer().getRefData().getPosition().pos; - Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); - sun = Vector3(sun.x, -sun.z, sun.y); - mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + if (!mRendering->occlusionQuerySupported()) + { + // cast a ray from player to sun to detect if the sun is visible + // this is temporary until we find a better place to put this code + // currently its here because we need to access the physics system + float* p = mPlayer->getPlayer().getRefData().getPosition().pos; + Vector3 sun = mRendering->getSkyManager()->getRealSunPos(); + sun = Vector3(sun.x, -sun.z, sun.y); + mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); + } } bool World::isCellExterior() const From e212a3235011986ad9fda7f58ac354c79e971f21 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 18:38:58 +0100 Subject: [PATCH 03/85] bugfix --- apps/openmw/mwrender/occlusionquery.cpp | 15 +++++++++++-- apps/openmw/mwrender/occlusionquery.hpp | 29 +++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 6c74561e95..baf55b0249 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -45,19 +45,21 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryVisible->setColourWriteEnabled(false); matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); mBBQueryTotal->setMaterialName("QueryTotalPixels"); mBBQueryTotal->setRenderQueueGroup(queue); - mSunNode->attachObject(mBBQueryTotal); + mBBNode->attachObject(mBBQueryTotal); mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); mBBQueryVisible->setDefaultDimensions(150, 150); mBBQueryVisible->createBillboard(Vector3::ZERO); mBBQueryVisible->setMaterialName("QueryVisiblePixels"); mBBQueryVisible->setRenderQueueGroup(queue); - mSunNode->attachObject(mBBQueryVisible); + mBBNode->attachObject(mBBQueryVisible); mRendering->getScene()->addRenderObjectListener(this); mDoQuery = true; @@ -110,6 +112,15 @@ void OcclusionQuery::update() { if (!mSupported) return; + // Adjust the position of the sun billboards according to camera viewing distance + // we need to do this to make sure that _everything_ can occlude the sun + float dist = mRendering->getCamera()->getFarClipDistance(); + if (dist==0) dist = 10000000; + dist -= 1000; // bias + dist /= 1000.f; + mBBNode->setPosition(mSunNode->getPosition() * dist); + mBBNode->setScale(dist, dist, dist); + // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) mDoQuery = false; diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index e6adc0d4b3..2545e590e9 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -17,11 +17,32 @@ namespace MWRender OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); ~OcclusionQuery(); + /** + * @return true if occlusion queries are supported on the user's hardware + */ bool supported(); - ///< returns true if occlusion queries are supported on the user's hardware + /** + * per-frame update + */ void update(); - ///< per-frame update + + /** + * request occlusion test for a billboard at the given position, omitting an entity + * @param position of the billboard in ogre coordinates + * @param entity to exclude from the occluders + */ + void occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity); + + /** + * @return true if a request is still outstanding + */ + bool occlusionTestPending(); + + /** + * @return true if the object tested in the last request was occluded + */ + bool getTestResult(); float getSunVisibility() const {return mSunVisibility;}; @@ -35,8 +56,12 @@ namespace MWRender Ogre::SceneNode* mSunNode; + Ogre::SceneNode* mBBNode; + float mSunVisibility; + bool mTestResult; + bool mSupported; bool mDoQuery; From 9d30a139cc416a078c6f63871e7d584f982fc2a1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 24 Mar 2012 20:41:23 +0100 Subject: [PATCH 04/85] added api --- apps/openmw/mwrender/occlusionquery.cpp | 73 +++++++++++++++++++++++-- apps/openmw/mwrender/occlusionquery.hpp | 20 ++++++- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index baf55b0249..8dd30394c9 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -3,12 +3,16 @@ #include #include #include +#include +#include using namespace MWRender; using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : - mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0) + mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), + mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), + mQuerySingleObjectRequested(false) { mRendering = renderer; mSunNode = sunNode; @@ -18,8 +22,9 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery(); mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery(); + mSingleObjectQuery = renderSystem->createHardwareOcclusionQuery(); - mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0); + mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0) && (mSingleObjectQuery != 0); } catch (Ogre::Exception e) { @@ -47,6 +52,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); @@ -61,6 +68,13 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBQueryVisible->setRenderQueueGroup(queue); mBBNode->attachObject(mBBQueryVisible); + mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); + mBBQuerySingleObject->setDefaultDimensions(10, 10); + mBBQuerySingleObject->createBillboard(Vector3::ZERO); + mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); + mBBQuerySingleObject->setRenderQueueGroup(queue); + mObjectNode->attachObject(mBBQuerySingleObject); + mRendering->getScene()->addRenderObjectListener(this); mDoQuery = true; } @@ -70,6 +84,7 @@ OcclusionQuery::~OcclusionQuery() RenderSystem* renderSystem = Root::getSingleton().getRenderSystem(); if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery); if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery); + if (mSingleObjectQuery) renderSystem->destroyHardwareOcclusionQuery(mSingleObjectQuery); } bool OcclusionQuery::supported() @@ -100,11 +115,15 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunTotalAreaQuery; else if (rend == mBBQueryVisible) mActiveQuery = mSunVisibleAreaQuery; + else if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + { + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + mActiveQuery = mSingleObjectQuery; + } if (mActiveQuery != NULL) - { mActiveQuery->beginOcclusionQuery(); - } } } @@ -125,7 +144,9 @@ void OcclusionQuery::update() // (may not happen on the same frame they are requested in) mDoQuery = false; - if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) + if (!mSunTotalAreaQuery->isStillOutstanding() + && !mSunVisibleAreaQuery->isStillOutstanding() + && !mSingleObjectQuery->isStillOutstanding()) { unsigned int totalPixels; unsigned int visiblePixels; @@ -144,7 +165,49 @@ void OcclusionQuery::update() if (mSunVisibility > 1) mSunVisibility = 1; } + if (mQuerySingleObjectStarted) + { + unsigned int visiblePixels; + + mSingleObjectQuery->pullOcclusionQuery(&visiblePixels); + + mBBQuerySingleObject->setVisible(false); + mObject->setRenderQueueGroup(mObjectOldRenderQueue); + + mQuerySingleObjectStarted = false; + mQuerySingleObjectRequested = false; + } + mDoQuery = true; } } +void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity) +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + mBBQuerySingleObject->setVisible(true); + + // we don't want the object to occlude itself + mObjectOldRenderQueue = entity->getRenderQueueGroup(); + if (mObjectOldRenderQueue < RENDER_QUEUE_MAIN+2) + entity->setRenderQueueGroup(RENDER_QUEUE_MAIN+2); + + mObjectNode->setPosition(position); + + mQuerySingleObjectRequested = true; +} + +bool OcclusionQuery::occlusionTestPending() +{ + return (mQuerySingleObjectRequested || mQuerySingleObjectStarted); +} + +bool OcclusionQuery::getTestResult() +{ + assert( !occlusionTestPending() + && "Occlusion test still pending"); + + return mTestResult; +} diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 2545e590e9..bcc45d96c6 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -1,9 +1,15 @@ #ifndef _GAME_OCCLUSION_QUERY_H #define _GAME_OCCLUSION_QUERY_H -#include #include +namespace Ogre +{ + class HardwareOcclusionQuery; + class Entity; + class SceneNode; +} + #include namespace MWRender @@ -49,22 +55,30 @@ namespace MWRender private: Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; + Ogre::HardwareOcclusionQuery* mSingleObjectQuery; Ogre::HardwareOcclusionQuery* mActiveQuery; Ogre::BillboardSet* mBBQueryVisible; Ogre::BillboardSet* mBBQueryTotal; + Ogre::BillboardSet* mBBQuerySingleObject; Ogre::SceneNode* mSunNode; - Ogre::SceneNode* mBBNode; - float mSunVisibility; + Ogre::Entity* mObject; + + Ogre::SceneNode* mObjectNode; + int mObjectOldRenderQueue; + bool mTestResult; bool mSupported; bool mDoQuery; + bool mQuerySingleObjectRequested; + bool mQuerySingleObjectStarted; + OEngine::Render::OgreRenderer* mRendering; protected: From 17a4adfe88aded63fd21fe5d3ab658aa62c354c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 12:26:44 +0200 Subject: [PATCH 05/85] add raycast method that returns _all_ objects that were hit --- libs/openengine/bullet/physic.cpp | 31 +++++++++++++++++++++++++++++++ libs/openengine/bullet/physic.hpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 07bad30535..e6a7393cca 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -372,4 +372,35 @@ namespace Physic return std::pair(name,d); } + + std::vector< std::pair > PhysicEngine::rayTest2(btVector3& from, btVector3& to) + { + MyRayResultCallback resultCallback1; + resultCallback1.m_collisionFilterMask = COL_WORLD; + dynamicsWorld->rayTest(from, to, resultCallback1); + resultCallback1.sort(); + std::vector< std::pair > results = resultCallback1.results; + + MyRayResultCallback resultCallback2; + resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + dynamicsWorld->rayTest(from, to, resultCallback2); + resultCallback2.sort(); + std::vector< std::pair > actorResults = resultCallback2.results; + + std::vector< std::pair > results2; + + for (std::vector< std::pair >::iterator it=results.begin(); + it != results.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + for (std::vector< std::pair >::iterator it=actorResults.begin(); + it != actorResults.end(); ++it) + { + results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); + } + + return results2; + } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 88e3699aee..97d2c004d1 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -201,6 +201,11 @@ namespace Physic */ std::pair rayTest(btVector3& from,btVector3& to); + /** + * Return all objects hit by a ray. + */ + std::vector< std::pair > rayTest2(btVector3& from, btVector3& to); + //event list of non player object std::list NPEventList; @@ -225,6 +230,30 @@ namespace Physic bool isDebugCreated; }; + + struct MyRayResultCallback : public btCollisionWorld::RayResultCallback + { + virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool bNormalInWorldSpace) + { + results.push_back( std::make_pair(rayResult.m_hitFraction, rayResult.m_collisionObject) ); + return rayResult.m_hitFraction; + } + + static bool cmp( const std::pair& i, const std::pair& j ) + { + if( i.first < j.first ) return false; + if( j.first < i.first ) return true; + return false; + } + + void sort() + { + std::sort(results.begin(), results.end(), cmp); + } + + std::vector < std::pair > results; + }; + }} #endif From 53d4be5cf650edfb0730aa3424d19e70ebfcec40 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 20:52:56 +0200 Subject: [PATCH 06/85] object pickup should work everywhere --- apps/openmw/mwrender/occlusionquery.cpp | 105 +++++++++++++++------- apps/openmw/mwrender/occlusionquery.hpp | 22 +++-- apps/openmw/mwrender/renderingmanager.hpp | 1 + apps/openmw/mwworld/physicssystem.cpp | 27 ++++++ apps/openmw/mwworld/physicssystem.hpp | 6 +- apps/openmw/mwworld/world.cpp | 73 +++++++++++++-- apps/openmw/mwworld/world.hpp | 6 ++ libs/openengine/bullet/physic.hpp | 4 +- 8 files changed, 198 insertions(+), 46 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 8dd30394c9..66086a90b8 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -47,7 +47,7 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels"); matQueryVisible->setDepthWriteEnabled(false); - matQueryVisible->setColourWriteEnabled(false); + matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); @@ -69,13 +69,14 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); - mBBQuerySingleObject->setDefaultDimensions(10, 10); + mBBQuerySingleObject->setDefaultDimensions(0.01, 0.01); mBBQuerySingleObject->createBillboard(Vector3::ZERO); mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); mBBQuerySingleObject->setRenderQueueGroup(queue); mObjectNode->attachObject(mBBQuerySingleObject); mRendering->getScene()->addRenderObjectListener(this); + mRendering->getScene()->addRenderQueueListener(this); mDoQuery = true; } @@ -95,8 +96,6 @@ bool OcclusionQuery::supported() void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, const LightList* pLightList, bool suppressRenderStateChanges) { - if (!mSupported) return; - // The following code activates and deactivates the occlusion queries // so that the queries only include the rendering of their intended targets @@ -111,26 +110,47 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass // Open a new occlusion query if (mDoQuery == true) { - if (rend == mBBQueryTotal) - mActiveQuery = mSunTotalAreaQuery; - else if (rend == mBBQueryVisible) - mActiveQuery = mSunVisibleAreaQuery; - else if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + if (rend == mBBQueryTotal) { - mQuerySingleObjectStarted = true; - mQuerySingleObjectRequested = false; - mActiveQuery = mSingleObjectQuery; + mActiveQuery = mSunTotalAreaQuery; + mWasVisible = true; + } + else if (rend == mBBQueryVisible) + { + mActiveQuery = mSunVisibleAreaQuery; } - - if (mActiveQuery != NULL) - mActiveQuery->beginOcclusionQuery(); } + if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + { + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + mActiveQuery = mSingleObjectQuery; + } + + if (mActiveQuery != NULL) + mActiveQuery->beginOcclusionQuery(); +} + +void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) +{ + if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false) + { + // for some reason our single object query returns wrong results when the sun query was never executed + // (which can happen when we are in interiors, or when the sun is outside of the view frustum and gets culled) + // so we force it here once everything has been rendered + mSunTotalAreaQuery->beginOcclusionQuery(); + mSunTotalAreaQuery->endOcclusionQuery(); + mSunVisibleAreaQuery->beginOcclusionQuery(); + mSunVisibleAreaQuery->endOcclusionQuery(); + } } void OcclusionQuery::update() { if (!mSupported) return; + mWasVisible = false; + // Adjust the position of the sun billboards according to camera viewing distance // we need to do this to make sure that _everything_ can occlude the sun float dist = mRendering->getCamera()->getFarClipDistance(); @@ -145,8 +165,7 @@ void OcclusionQuery::update() mDoQuery = false; if (!mSunTotalAreaQuery->isStillOutstanding() - && !mSunVisibleAreaQuery->isStillOutstanding() - && !mSingleObjectQuery->isStillOutstanding()) + && !mSunVisibleAreaQuery->isStillOutstanding()) { unsigned int totalPixels; unsigned int visiblePixels; @@ -165,24 +184,33 @@ void OcclusionQuery::update() if (mSunVisibility > 1) mSunVisibility = 1; } - if (mQuerySingleObjectStarted) + mDoQuery = true; + } + if (!mSingleObjectQuery->isStillOutstanding() && mQuerySingleObjectStarted) + { + unsigned int result; + + mSingleObjectQuery->pullOcclusionQuery(&result); + + //std::cout << "Single object query result: " << result << " pixels " << std::endl; + mTestResult = (result != 0); + + mBBQuerySingleObject->setVisible(false); + + // restore old render queues + for (std::vector::iterator it=mObjectsInfo.begin(); + it!=mObjectsInfo.end(); ++it) { - unsigned int visiblePixels; - - mSingleObjectQuery->pullOcclusionQuery(&visiblePixels); - - mBBQuerySingleObject->setVisible(false); - mObject->setRenderQueueGroup(mObjectOldRenderQueue); - - mQuerySingleObjectStarted = false; - mQuerySingleObjectRequested = false; + if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) return; + mRendering->getScene()->getMovableObject((*it).name, (*it).typeName)->setRenderQueueGroup( (*it).oldRenderqueue ); } - mDoQuery = true; + mQuerySingleObjectStarted = false; + mQuerySingleObjectRequested = false; } } -void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity) +void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object) { assert( !occlusionTestPending() && "Occlusion test still pending"); @@ -190,11 +218,24 @@ void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::Entity* mBBQuerySingleObject->setVisible(true); // we don't want the object to occlude itself - mObjectOldRenderQueue = entity->getRenderQueueGroup(); - if (mObjectOldRenderQueue < RENDER_QUEUE_MAIN+2) - entity->setRenderQueueGroup(RENDER_QUEUE_MAIN+2); + // put it in a render queue _after_ the occlusion query + mObjectsInfo.clear(); + for (int i=0; inumAttachedObjects(); ++i) + { + ObjectInfo info; + MovableObject* obj = object->getAttachedObject(i); + info.name = obj->getName(); + info.typeName = obj->getMovableType(); + info.oldRenderqueue = obj->getRenderQueueGroup(); + + mObjectsInfo.push_back(info); + + object->getAttachedObject(i)->setRenderQueueGroup(RENDER_QUEUE_MAIN+5); + } mObjectNode->setPosition(position); + // scale proportional to camera distance, in order to always give the billboard the same size in screen-space + mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() ); mQuerySingleObjectRequested = true; } diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index bcc45d96c6..987087e278 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -2,6 +2,7 @@ #define _GAME_OCCLUSION_QUERY_H #include +#include namespace Ogre { @@ -17,7 +18,14 @@ namespace MWRender /// /// \brief Implements hardware occlusion queries on the GPU /// - class OcclusionQuery : public Ogre::RenderObjectListener + struct ObjectInfo + { + int oldRenderqueue; + std::string name; + std::string typeName; + }; + + class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener { public: OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode); @@ -36,9 +44,9 @@ namespace MWRender /** * request occlusion test for a billboard at the given position, omitting an entity * @param position of the billboard in ogre coordinates - * @param entity to exclude from the occluders + * @param object to exclude from the occluders */ - void occlusionTest(const Ogre::Vector3& position, Ogre::Entity* entity); + void occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object); /** * @return true if a request is still outstanding @@ -66,10 +74,10 @@ namespace MWRender Ogre::SceneNode* mBBNode; float mSunVisibility; - Ogre::Entity* mObject; - Ogre::SceneNode* mObjectNode; - int mObjectOldRenderQueue; + std::vector mObjectsInfo; + + bool mWasVisible; bool mTestResult; @@ -84,6 +92,8 @@ namespace MWRender protected: virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source, const Ogre::LightList* pLightList, bool suppressRenderStateChanges); + + virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation); }; } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 450e461730..569447bca3 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -100,6 +100,7 @@ class RenderingManager: private RenderingInterface { void sunDisable(); bool occlusionQuerySupported() { return mOcclusionQuery->supported(); }; + OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; }; void setGlare(bool glare); void skyEnable (); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index bb2f9f8a92..1d8f14cc42 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -50,6 +50,33 @@ namespace MWWorld return mEngine->rayTest(from,to); } + + std::vector < std::pair > PhysicsSystem::getFacedObjects () + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay( + mRender.getViewport()->getWidth()/2, + mRender.getViewport()->getHeight()/2); + //let's avoid the capsule shape of the player. + centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); + btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); + btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); + + return mEngine->rayTest2(from,to); + } + + btVector3 PhysicsSystem::getRayPoint(float extent) + { + //get a ray pointing to the center of the viewport + Ray centerRay = mRender.getCamera()->getCameraToViewportRay( + mRender.getViewport()->getWidth()/2, + mRender.getViewport()->getHeight()/2); + //let's avoid the capsule shape of the player. + centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); + btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); + btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); + return from * (1-extent) + to * extent; + } bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to) { diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 78cbde0837..7b2d77325e 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -35,7 +35,11 @@ namespace MWWorld bool toggleCollisionMode(); std::pair getFacedHandle (MWWorld::World& world); - + + btVector3 getRayPoint(float extent); + + std::vector < std::pair > getFacedObjects (); + // cast ray, return true if it hit something bool castRay(const Ogre::Vector3& from, const Ogre::Vector3& to); diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 261f66ff4f..f185e411f8 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -498,13 +498,21 @@ namespace MWWorld std::string World::getFacedHandle() { - std::pair result = mPhysics->getFacedHandle (*this); + if (!mRendering->occlusionQuerySupported()) + { + std::pair result = mPhysics->getFacedHandle (*this); - if (result.first.empty() || - result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) - return ""; + if (result.first.empty() || + result.second>getStore().gameSettings.find ("iMaxActivateDist")->i) + return ""; - return result.first; + return result.first; + } + else + { + // updated every few frames in update() + return mFacedHandle; + } } void World::deleteObject (Ptr ptr) @@ -715,6 +723,61 @@ namespace MWWorld sun = Vector3(sun.x, -sun.z, sun.y); mRendering->getSkyManager()->setGlare(!mPhysics->castRay(Ogre::Vector3(p[0], p[1], p[2]), sun)); } + + // update faced handle (object the player is looking at) + // this uses a mixture of raycasts and occlusion queries. + else // if (mRendering->occlusionQuerySupported()) + { + MWRender::OcclusionQuery* query = mRendering->getOcclusionQuery(); + if (!query->occlusionTestPending()) + { + // get result of last query + if (mNumFacing == 0) mFacedHandle = ""; + else if (mNumFacing == 1) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced1Name : ""; + } + else if (mNumFacing == 2) + { + bool result = query->getTestResult(); + mFacedHandle = result ? mFaced2Name : mFaced1Name; + } + + // send new query + // figure out which object we want to test against + std::vector < std::pair < float, std::string > > results = mPhysics->getFacedObjects(); + + if (results.size() == 0) + { + mNumFacing = 0; + } + else if (results.size() == 1) + { + mFaced1 = getPtrViaHandle(results.front().second); + mFaced1Name = results.front().second; + mNumFacing = 1; + + btVector3 p = mPhysics->getRayPoint(results.front().first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node = mFaced1.getRefData().getBaseNode(); + query->occlusionTest(pos, node); + } + else + { + mFaced1Name = results.front().second; + mFaced2Name = results[1].second; + mFaced1 = getPtrViaHandle(results.front().second); + mFaced2 = getPtrViaHandle(results[1].second); + mNumFacing = 2; + + btVector3 p = mPhysics->getRayPoint(results[1].first); + Ogre::Vector3 pos(p.x(), p.z(), -p.y()); + Ogre::SceneNode* node = mFaced2.getRefData().getBaseNode(); + query->occlusionTest(pos, node); + } + } + } } bool World::isCellExterior() const diff --git a/apps/openmw/mwworld/world.hpp b/apps/openmw/mwworld/world.hpp index 71cca3545d..7d8a1b9a0a 100644 --- a/apps/openmw/mwworld/world.hpp +++ b/apps/openmw/mwworld/world.hpp @@ -93,6 +93,12 @@ namespace MWWorld Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore); + std::string mFacedHandle; + Ptr mFaced1; + Ptr mFaced2; + std::string mFaced1Name; + std::string mFaced2Name; + int mNumFacing; int getDaysPerMonth (int month) const; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 97d2c004d1..3ecae7c0cc 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -241,8 +241,8 @@ namespace Physic static bool cmp( const std::pair& i, const std::pair& j ) { - if( i.first < j.first ) return false; - if( j.first < i.first ) return true; + if( i.first > j.first ) return false; + if( j.first > i.first ) return true; return false; } From 382fa6ac688097eb2980067255f4b8fe9395f3cd Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 22:53:00 +0200 Subject: [PATCH 07/85] fixes --- apps/openmw/mwrender/occlusionquery.cpp | 10 +++++++--- libs/openengine/bullet/physic.cpp | 4 ++-- libs/openengine/bullet/physic.hpp | 7 +------ 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 66086a90b8..b5f3f9c102 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -49,6 +49,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryVisible->setDepthWriteEnabled(false); matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects + matQueryVisible->setCullingMode(CULL_NONE); + matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE); mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); @@ -133,16 +135,18 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) { - if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false) + if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false && mDoQuery) { // for some reason our single object query returns wrong results when the sun query was never executed // (which can happen when we are in interiors, or when the sun is outside of the view frustum and gets culled) // so we force it here once everything has been rendered + mSunTotalAreaQuery->beginOcclusionQuery(); mSunTotalAreaQuery->endOcclusionQuery(); mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); - } + + } } void OcclusionQuery::update() @@ -201,7 +205,7 @@ void OcclusionQuery::update() for (std::vector::iterator it=mObjectsInfo.begin(); it!=mObjectsInfo.end(); ++it) { - if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) return; + if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) break; mRendering->getScene()->getMovableObject((*it).name, (*it).typeName)->setRenderQueueGroup( (*it).oldRenderqueue ); } diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index e6a7393cca..b1cd9eadee 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -378,13 +378,11 @@ namespace Physic MyRayResultCallback resultCallback1; resultCallback1.m_collisionFilterMask = COL_WORLD; dynamicsWorld->rayTest(from, to, resultCallback1); - resultCallback1.sort(); std::vector< std::pair > results = resultCallback1.results; MyRayResultCallback resultCallback2; resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; dynamicsWorld->rayTest(from, to, resultCallback2); - resultCallback2.sort(); std::vector< std::pair > actorResults = resultCallback2.results; std::vector< std::pair > results2; @@ -401,6 +399,8 @@ namespace Physic results2.push_back( std::make_pair( (*it).first, static_cast(*(*it).second).mName ) ); } + std::sort(results2.begin(), results2.end(), MyRayResultCallback::cmp); + return results2; } }}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 3ecae7c0cc..f6c52cbf95 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -239,18 +239,13 @@ namespace Physic return rayResult.m_hitFraction; } - static bool cmp( const std::pair& i, const std::pair& j ) + static bool cmp( const std::pair& i, const std::pair& j ) { if( i.first > j.first ) return false; if( j.first > i.first ) return true; return false; } - void sort() - { - std::sort(results.begin(), results.end(), cmp); - } - std::vector < std::pair > results; }; From 978b5f64d8767ef05b1010ecd8f862d36bba1b65 Mon Sep 17 00:00:00 2001 From: Roman Melnik Date: Sun, 25 Mar 2012 23:53:14 +0300 Subject: [PATCH 08/85] Merge cmake/findBullet patch for '_debug' postfixed libraries Merge in the patch from the cmake official repository: https://cmake.org/gitweb?p=cmake.git;a=commit;h=6f935d7f55af5c96d18d23b72991e87d1e029dda Without this fix cmake couldn't find bullet's debug libraries, and VS2010 openmw project file was generated with links to release libraries instead (which caused errors during build in debug configuration). --- cmake/FindBullet.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/FindBullet.cmake b/cmake/FindBullet.cmake index 7bdf75a0a3..552a0651af 100644 --- a/cmake/FindBullet.cmake +++ b/cmake/FindBullet.cmake @@ -51,13 +51,13 @@ find_path(BULLET_INCLUDE_DIR NAMES btBulletCollisionCommon.h # Find the libraries _FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY BulletDynamics) -_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_d) +_FIND_BULLET_LIBRARY(BULLET_DYNAMICS_LIBRARY_DEBUG BulletDynamics_Debug BulletDynamics_d) _FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY BulletCollision) -_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_d) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY LinearMath BulletMath) -_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG LinearMath_d BulletMath_d) +_FIND_BULLET_LIBRARY(BULLET_COLLISION_LIBRARY_DEBUG BulletCollision_Debug BulletCollision_d) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY BulletMath LinearMath) +_FIND_BULLET_LIBRARY(BULLET_MATH_LIBRARY_DEBUG BulletMath_Debug BulletMath_d LinearMath_debug LinearMath_d) _FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY BulletSoftBody) -_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_d) +_FIND_BULLET_LIBRARY(BULLET_SOFTBODY_LIBRARY_DEBUG BulletSoftBody_Debug BulletSoftBody_d) # handle the QUIETLY and REQUIRED arguments and set BULLET_FOUND to TRUE if From 35c3a0a3f921ef767121fea17cf2ebf1f4808f7e Mon Sep 17 00:00:00 2001 From: Roman Melnik Date: Mon, 26 Mar 2012 00:12:00 +0300 Subject: [PATCH 09/85] Add Boost_USE_STATIC_LIBS parameter for Windows If this parameter is not set in windows, cmake expects boost libraries to be named "boost_*.lib", while they are actually "libboost_*.lib" In findBoost.cmake (728): # Setting some more suffixes for the library set (Boost_LIB_PREFIX "") if (WIN32 AND Boost_USE_STATIC_LIBS AND NOT CYGWIN) set(Boost_LIB_PREFIX "lib") endif() --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f0bffa8f..d2801c8f22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,7 @@ set(OPENMW_LIBS_HEADER) # Platform specific if (WIN32) + set(Boost_USE_STATIC_LIBS ON) set(PLATFORM_INCLUDE_DIR "platform") add_definitions(-DBOOST_ALL_NO_LIB) else (WIN32) From 98a33a7fc6c7f9e3f75f8ccbd93e7625a330eb1f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 25 Mar 2012 23:28:51 +0200 Subject: [PATCH 10/85] fix --- apps/openmw/mwrender/occlusionquery.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index b5f3f9c102..971815f9bf 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -71,7 +71,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); - mBBQuerySingleObject->setDefaultDimensions(0.01, 0.01); + /// \todo ideally this should occupy exactly 1 pixel on the screen + mBBQuerySingleObject->setDefaultDimensions(0.003, 0.003); mBBQuerySingleObject->createBillboard(Vector3::ZERO); mBBQuerySingleObject->setMaterialName("QueryVisiblePixels"); mBBQuerySingleObject->setRenderQueueGroup(queue); From 4ee03cd61fef8d71e0a4614ef512e34ce30fa278 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 26 Mar 2012 00:31:03 +0200 Subject: [PATCH 11/85] handle timeout --- apps/openmw/mwrender/occlusionquery.cpp | 16 +++++++++++++--- apps/openmw/mwrender/occlusionquery.hpp | 6 +++++- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 971815f9bf..6718a588bc 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), - mQuerySingleObjectRequested(false) + mQuerySingleObjectRequested(false), mResponding(true), mDelay(0) { mRendering = renderer; mSunNode = sunNode; @@ -93,7 +93,8 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { - return mSupported; + if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; + return mSupported && mResponding; } void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, @@ -150,10 +151,13 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati } } -void OcclusionQuery::update() +void OcclusionQuery::update(float duration) { if (!mSupported) return; + mDelay += duration; + if (mDelay >= 2) mResponding = false; + mWasVisible = false; // Adjust the position of the sun billboards according to camera viewing distance @@ -172,6 +176,9 @@ void OcclusionQuery::update() if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) { + mDelay = 0; + mResponding = true; + unsigned int totalPixels; unsigned int visiblePixels; @@ -193,6 +200,9 @@ void OcclusionQuery::update() } if (!mSingleObjectQuery->isStillOutstanding() && mQuerySingleObjectStarted) { + mDelay = 0; + mResponding = true; + unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 987087e278..004190cd90 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -39,7 +39,7 @@ namespace MWRender /** * per-frame update */ - void update(); + void update(float duration); /** * request occlusion test for a billboard at the given position, omitting an entity @@ -79,6 +79,10 @@ namespace MWRender bool mWasVisible; + bool mResponding; + + float mDelay; + bool mTestResult; bool mSupported; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b4711e8df7..2aec95e813 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -150,7 +150,7 @@ void RenderingManager::update (float duration){ mActors.update (duration); - mOcclusionQuery->update(); + mOcclusionQuery->update(duration); mSkyManager->update(duration); From bb3f3ce1db69ab935320bd9ddff0d540ccc25bc0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 26 Mar 2012 21:52:38 +0200 Subject: [PATCH 12/85] bugfix --- apps/openmw/mwrender/occlusionquery.cpp | 39 ++++++++++++++++--------- apps/openmw/mwrender/occlusionquery.hpp | 1 + apps/openmw/mwrender/sky.cpp | 3 +- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 6718a588bc..c3285a3359 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), - mQuerySingleObjectRequested(false), mResponding(true), mDelay(0) + mQuerySingleObjectRequested(false), mResponding(true), mDelay(0), mWasVisible(false), mObjectWasVisible(false) { mRendering = renderer; mSunNode = sunNode; @@ -93,7 +93,7 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { - if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; + //if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; return mSupported && mResponding; } @@ -129,6 +129,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; mActiveQuery = mSingleObjectQuery; + mObjectWasVisible = true; } if (mActiveQuery != NULL) @@ -136,18 +137,29 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass } void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) -{ - if (queueGroupId == RENDER_QUEUE_SKIES_LATE && mWasVisible == false && mDoQuery) +{ + /** + * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa + * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called + * this can happen for example if the object that is tested is outside of the view frustum + * to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually + */ + if (queueGroupId == RENDER_QUEUE_SKIES_LATE) { - // for some reason our single object query returns wrong results when the sun query was never executed - // (which can happen when we are in interiors, or when the sun is outside of the view frustum and gets culled) - // so we force it here once everything has been rendered - - mSunTotalAreaQuery->beginOcclusionQuery(); - mSunTotalAreaQuery->endOcclusionQuery(); - mSunVisibleAreaQuery->beginOcclusionQuery(); - mSunVisibleAreaQuery->endOcclusionQuery(); - + if (mWasVisible == false && mDoQuery) + { + mSunTotalAreaQuery->beginOcclusionQuery(); + mSunTotalAreaQuery->endOcclusionQuery(); + mSunVisibleAreaQuery->beginOcclusionQuery(); + mSunVisibleAreaQuery->endOcclusionQuery(); + } + if (mObjectWasVisible == false && mQuerySingleObjectRequested) + { + mSingleObjectQuery->beginOcclusionQuery(); + mSingleObjectQuery->endOcclusionQuery(); + mQuerySingleObjectStarted = true; + mQuerySingleObjectRequested = false; + } } } @@ -159,6 +171,7 @@ void OcclusionQuery::update(float duration) if (mDelay >= 2) mResponding = false; mWasVisible = false; + mObjectWasVisible = false; // Adjust the position of the sun billboards according to camera viewing distance // we need to do this to make sure that _everything_ can occlude the sun diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 004190cd90..a9fa0b5e4a 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -78,6 +78,7 @@ namespace MWRender std::vector mObjectsInfo; bool mWasVisible; + bool mObjectWasVisible; bool mResponding; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a70913b239..954b462095 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -294,7 +294,8 @@ void SkyManager::ModVertexAlpha(Entity* ent, unsigned int meshType) } SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environment* env) : - mGlare(0), mGlareFade(0) + mGlare(0), mGlareFade(0), mCloudBlendFactor(0), + mCloudOpacity(0), mCloudColour(1,1,1), mSkyColour(1,1,1), mCloudSpeed(0), mStarsOpacity(0) { mEnvironment = env; mViewport = pCamera->getViewport(); From e398c51f8a9b641b43658d14639f67301f8d0e11 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 27 Mar 2012 13:13:28 +0200 Subject: [PATCH 13/85] remove unneeded render queue workaround --- apps/openmw/mwrender/occlusionquery.cpp | 24 ------------------------ apps/openmw/mwrender/occlusionquery.hpp | 8 -------- 2 files changed, 32 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index c3285a3359..43a742a548 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -225,14 +225,6 @@ void OcclusionQuery::update(float duration) mBBQuerySingleObject->setVisible(false); - // restore old render queues - for (std::vector::iterator it=mObjectsInfo.begin(); - it!=mObjectsInfo.end(); ++it) - { - if (!mRendering->getScene()->hasMovableObject((*it).name, (*it).typeName)) break; - mRendering->getScene()->getMovableObject((*it).name, (*it).typeName)->setRenderQueueGroup( (*it).oldRenderqueue ); - } - mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; } @@ -245,22 +237,6 @@ void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNod mBBQuerySingleObject->setVisible(true); - // we don't want the object to occlude itself - // put it in a render queue _after_ the occlusion query - mObjectsInfo.clear(); - for (int i=0; inumAttachedObjects(); ++i) - { - ObjectInfo info; - MovableObject* obj = object->getAttachedObject(i); - info.name = obj->getName(); - info.typeName = obj->getMovableType(); - info.oldRenderqueue = obj->getRenderQueueGroup(); - - mObjectsInfo.push_back(info); - - object->getAttachedObject(i)->setRenderQueueGroup(RENDER_QUEUE_MAIN+5); - } - mObjectNode->setPosition(position); // scale proportional to camera distance, in order to always give the billboard the same size in screen-space mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() ); diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index a9fa0b5e4a..b3e5442cfe 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -18,13 +18,6 @@ namespace MWRender /// /// \brief Implements hardware occlusion queries on the GPU /// - struct ObjectInfo - { - int oldRenderqueue; - std::string name; - std::string typeName; - }; - class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener { public: @@ -75,7 +68,6 @@ namespace MWRender float mSunVisibility; Ogre::SceneNode* mObjectNode; - std::vector mObjectsInfo; bool mWasVisible; bool mObjectWasVisible; From 8f6d10f229a45bfa2c01364a4a0e9d1a2a78200e Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 27 Mar 2012 20:59:58 +0200 Subject: [PATCH 14/85] fix uninitalised stuff --- apps/openmw/mwrender/occlusionquery.cpp | 4 ++-- apps/openmw/mwworld/world.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 43a742a548..2c184c3451 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -11,7 +11,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), - mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), + mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), mQuerySingleObjectRequested(false), mResponding(true), mDelay(0), mWasVisible(false), mObjectWasVisible(false) { mRendering = renderer; @@ -211,7 +211,7 @@ void OcclusionQuery::update(float duration) mDoQuery = true; } - if (!mSingleObjectQuery->isStillOutstanding() && mQuerySingleObjectStarted) + if (mQuerySingleObjectStarted && !mSingleObjectQuery->isStillOutstanding()) { mDelay = 0; mResponding = true; diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index f185e411f8..567ed8f90b 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -157,7 +157,8 @@ namespace MWWorld const std::string& master, const boost::filesystem::path& resDir, bool newGame, Environment& environment, const std::string& encoding) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), - mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this) + mSky (true), mEnvironment (environment), mNextDynamicRecord (0), mCells (mStore, mEsm, *this), + mNumFacing(0) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); From 43b1f896f42f986c6c19daaa3f3f78e6215e0953 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 27 Mar 2012 22:36:02 +0200 Subject: [PATCH 15/85] fix object pickup when player is very close to object --- apps/openmw/mwworld/physicssystem.cpp | 9 ++------- apps/openmw/mwworld/world.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 1d8f14cc42..83c3ef2bab 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -57,8 +57,6 @@ namespace MWWorld Ray centerRay = mRender.getCamera()->getCameraToViewportRay( mRender.getViewport()->getWidth()/2, mRender.getViewport()->getHeight()/2); - //let's avoid the capsule shape of the player. - centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); @@ -71,11 +69,8 @@ namespace MWWorld Ray centerRay = mRender.getCamera()->getCameraToViewportRay( mRender.getViewport()->getWidth()/2, mRender.getViewport()->getHeight()/2); - //let's avoid the capsule shape of the player. - centerRay.setOrigin(centerRay.getOrigin() + 20*centerRay.getDirection()); - btVector3 from(centerRay.getOrigin().x,-centerRay.getOrigin().z,centerRay.getOrigin().y); - btVector3 to(centerRay.getPoint(500).x,-centerRay.getPoint(500).z,centerRay.getPoint(500).y); - return from * (1-extent) + to * extent; + btVector3 result(centerRay.getPoint(500*extent).x,-centerRay.getPoint(500*extent).z,centerRay.getPoint(500*extent).y); + return result; } bool PhysicsSystem::castRay(const Vector3& from, const Vector3& to) diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 567ed8f90b..dd30447338 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -749,6 +749,17 @@ namespace MWWorld // figure out which object we want to test against std::vector < std::pair < float, std::string > > results = mPhysics->getFacedObjects(); + // ignore the player + for (std::vector < std::pair < float, std::string > >::iterator it = results.begin(); + it != results.end(); ++it) + { + if ( (*it).second == mPlayer->getPlayer().getRefData().getHandle() ) + { + results.erase(it); + break; + } + } + if (results.size() == 0) { mNumFacing = 0; From 47c3e92db1b3264112657ea6f10178091fe0cf37 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 28 Mar 2012 21:42:41 +0200 Subject: [PATCH 16/85] removed the unused ray scene query --- apps/openmw/mwrender/renderingmanager.cpp | 3 --- apps/openmw/mwrender/renderingmanager.hpp | 1 - 2 files changed, 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2dc35d8c84..00b43cf9fe 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -46,9 +46,6 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const mMwRoot->pitch(Degree(-90)); mObjects.setMwRoot(mMwRoot); mActors.setMwRoot(mMwRoot); - - //used to obtain ingame information of ogre objects (which are faced or selected) - mRaySceneQuery = mRendering.getScene()->createRayQuery(Ray()); Ogre::SceneNode *playerNode = mMwRoot->createChildSceneNode ("player"); playerNode->pitch(Degree(90)); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 569447bca3..edbc9aff27 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -158,7 +158,6 @@ class RenderingManager: private RenderingInterface { /// that the OGRE coordinate system matches that used internally in /// Morrowind. Ogre::SceneNode *mMwRoot; - Ogre::RaySceneQuery *mRaySceneQuery; OEngine::Physic::PhysicEngine* mPhysicsEngine; From e5a19209a4a8580dba93ca9560617e7dccf2306b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 29 Mar 2012 00:05:48 +0200 Subject: [PATCH 17/85] remove the timeout stuff --- apps/openmw/mwrender/occlusionquery.cpp | 13 ++----------- apps/openmw/mwrender/occlusionquery.hpp | 4 ---- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 2c184c3451..228d8a4990 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), - mQuerySingleObjectRequested(false), mResponding(true), mDelay(0), mWasVisible(false), mObjectWasVisible(false) + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false) { mRendering = renderer; mSunNode = sunNode; @@ -94,7 +94,7 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { //if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; - return mSupported && mResponding; + return mSupported; } void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source, @@ -167,9 +167,6 @@ void OcclusionQuery::update(float duration) { if (!mSupported) return; - mDelay += duration; - if (mDelay >= 2) mResponding = false; - mWasVisible = false; mObjectWasVisible = false; @@ -189,9 +186,6 @@ void OcclusionQuery::update(float duration) if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) { - mDelay = 0; - mResponding = true; - unsigned int totalPixels; unsigned int visiblePixels; @@ -213,9 +207,6 @@ void OcclusionQuery::update(float duration) } if (mQuerySingleObjectStarted && !mSingleObjectQuery->isStillOutstanding()) { - mDelay = 0; - mResponding = true; - unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index b3e5442cfe..e81358eb6c 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -72,10 +72,6 @@ namespace MWRender bool mWasVisible; bool mObjectWasVisible; - bool mResponding; - - float mDelay; - bool mTestResult; bool mSupported; From 0d163d76ab2aca24a092f54a940132c17bf38679 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 03:59:24 +0200 Subject: [PATCH 18/85] Replaced some messy code with some differently messy code, this one at least seems to work --- components/bsa/bsa_archive.cpp | 158 ++++++++++++++------------------- 1 file changed, 69 insertions(+), 89 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 80d92dd521..0ba93d4ad2 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -45,6 +45,32 @@ struct ciLessBoost : std::binary_function } }; +struct mrComparer +{ +private: + int m_start, m_size; + + bool comparePortion(std::string file1, std::string file2, int start, int size) const + { + for(int i = start; i < start+size; i++) + { + char one = tolower(file1.at(i)); + char two = tolower(file2.at(i)); + if(one != two) + return (one < two); + } + return true; + } + +public: + mrComparer(int start, int size) : m_start(start), m_size(size) { } + + bool operator() (const std::string& first, const std::string& other) + { + return comparePortion(first, other, m_start, m_size); + } +}; + static bool fsstrict = false; /// An OGRE Archive wrapping a BSAFile archive @@ -55,16 +81,46 @@ class DirArchive: public Ogre::FileSystemArchive std::map, ciLessBoost> m; unsigned int cutoff; - bool comparePortion(std::string file1, std::string file2, int start, int size) const + bool findFile(const String& filename, std::string& copy) const { - for(int i = start; i < start+size; i++) + copy = filename; + + std::replace(copy.begin(), copy.end(), '\\', '/'); + + if(copy.at(0) == '/') + copy.erase(0, 1); + + if(fsstrict == true) + return true; + + std::string folder; + int delimiter = 0; + size_t lastSlash = copy.rfind('/'); + if (lastSlash != std::string::npos) { - char one = file1.at(i); - char two = file2.at(i); - if(tolower(one) != tolower(two) ) - return false; + folder = copy.substr(0, lastSlash); + delimiter = lastSlash+1; } - return true; + + std::vector current; + { + std::map,ciLessBoost>::const_iterator found = m.find(folder); + if (found == m.end()) + { + std::replace(folder.begin(), folder.end(), '/', '\\'); + found = m.find(folder); + } + + current = found->second; + } + + mrComparer comp(delimiter, copy.size() - delimiter-1); + std::vector::iterator found = std::lower_bound(current.begin(), current.end(), copy, comp); + + if (found != current.end() && !(comp(copy, current.front()))) + return true; + + return false; } public: @@ -120,97 +176,21 @@ class DirArchive: public Ogre::FileSystemArchive void unload() {} bool exists(const String& filename) { - std::string copy = filename; - - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } - - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } - if(fsstrict == true) - { - //std::cout << "fsstrict " << copy << "\n"; + std::string copy; + + if (findFile(filename, copy)) return FileSystemArchive::exists(copy); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - - std::vector& current = m[folder]; - - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::exists(*iter); - } - } - return false; } DataStreamPtr open(const String& filename, bool readonly = true) const { - std::map, ciLessBoost> mlocal = m; - std::string copy = filename; + std::string copy; - - - for (unsigned int i = 0; i < filename.size(); i++) - { - if(copy.at(i) == '\\' ){ - copy.replace(i, 1, "/"); - } - } - - - if(copy.at(0) == '\\' || copy.at(0) == '/') - { - copy.erase(0, 1); - } - - if(fsstrict == true) - { + if (findFile(filename, copy)) return FileSystemArchive::open(copy, readonly); - } - - - int last = copy.size() - 1; - int i = last; - - for (;last >= 0; i--) - { - if(copy.at(i) == '/' || copy.at(i) == '\\') - break; - } - - std::string folder = copy.substr(0, i); //folder with no slash - std::vector current = mlocal[folder]; - - for(std::vector::iterator iter = current.begin(); iter != current.end(); iter++) - { - if(comparePortion(*iter, copy, i + 1, copy.size() - i -1) == true){ - return FileSystemArchive::open(*iter, readonly); - } - } + DataStreamPtr p; return p; } From 71cb85dbc48118f49fc987be053e751d2b7a43b6 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 04:54:33 +0200 Subject: [PATCH 19/85] Performance enhancment and minor copy-paste fix. --- components/bsa/bsa_archive.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 0ba93d4ad2..3981780bcb 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -50,7 +50,7 @@ struct mrComparer private: int m_start, m_size; - bool comparePortion(std::string file1, std::string file2, int start, int size) const + bool comparePortion(const std::string& file1, const std::string& file2, int start, int size) const { for(int i = start; i < start+size; i++) { @@ -59,7 +59,7 @@ private: if(one != two) return (one < two); } - return true; + return false; } public: From ce38876a74b09195d6d20dd82d49e944318b4080 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 17:31:55 +0200 Subject: [PATCH 20/85] Oops, that could've crashed horribly --- components/bsa/bsa_archive.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 3981780bcb..9a325d1605 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -111,7 +111,8 @@ class DirArchive: public Ogre::FileSystemArchive found = m.find(folder); } - current = found->second; + if (found != m.end()) + current = found->second; } mrComparer comp(delimiter, copy.size() - delimiter-1); From 3d5384e2bb350d42f0331fa77f59e3d5c804bb97 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 29 Mar 2012 11:28:33 -0700 Subject: [PATCH 21/85] Remove some unneeded casts --- apps/openmw/mwsound/soundmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index ad9e47f729..f072e084db 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -108,7 +108,7 @@ namespace MWSound max = std::max(min, max); } - return std::string("Sound/")+snd->sound; + return "Sound/"+snd->sound; } @@ -182,7 +182,7 @@ namespace MWSound { // The range values are not tested float basevol = 1.0f; /* TODO: volume settings */ - std::string filePath = std::string("Sound/")+filename; + std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getCellRef().pos; SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, From c3944d3e1a48220d423849472eb5aebc746185ea Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 21:27:37 +0200 Subject: [PATCH 22/85] Use a normal binary search --- components/bsa/bsa_archive.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 9a325d1605..a68e91df1e 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -116,12 +116,7 @@ class DirArchive: public Ogre::FileSystemArchive } mrComparer comp(delimiter, copy.size() - delimiter-1); - std::vector::iterator found = std::lower_bound(current.begin(), current.end(), copy, comp); - - if (found != current.end() && !(comp(copy, current.front()))) - return true; - - return false; + return std::binary_search(current.begin(), current.end(), copy, comp); } public: From 6acd900577639bc4d83d0a0fcc444f806ab4c2c7 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 21:36:00 +0200 Subject: [PATCH 23/85] Better name --- components/bsa/bsa_archive.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index a68e91df1e..945aae0773 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -45,7 +45,7 @@ struct ciLessBoost : std::binary_function } }; -struct mrComparer +struct pathComparer { private: int m_start, m_size; @@ -63,7 +63,7 @@ private: } public: - mrComparer(int start, int size) : m_start(start), m_size(size) { } + pathComparer(int start, int size) : m_start(start), m_size(size) { } bool operator() (const std::string& first, const std::string& other) { @@ -115,7 +115,7 @@ class DirArchive: public Ogre::FileSystemArchive current = found->second; } - mrComparer comp(delimiter, copy.size() - delimiter-1); + pathComparer comp(delimiter, copy.size() - delimiter-1); return std::binary_search(current.begin(), current.end(), copy, comp); } From 60b95e7992f04db17c2a50871e979e71c2ed29bd Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 29 Mar 2012 22:38:14 +0200 Subject: [PATCH 24/85] Sorting the file lists properly --- components/bsa/bsa_archive.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 945aae0773..64dc979803 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -155,6 +155,7 @@ class DirArchive: public Ogre::FileSystemArchive filesind.push_back(small); } } + std::sort(filesind.begin(), filesind.end(), ciLessBoost()); std::string small; std::string original = d.string(); if(cutoff < original.size()) From 4a9a416d46bb0bd7f2b92615dbde3b81b4975745 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 14:45:32 +0200 Subject: [PATCH 25/85] Can find files even if no .bsa file exists now --- apps/openmw/engine.cpp | 9 +++++++-- components/bsa/bsa_archive.cpp | 20 +++++++++++++++++++- components/files/collections.cpp | 5 +++++ components/files/collections.hpp | 2 ++ components/nifogre/ogre_nif_loader.cpp | 2 +- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5e49ae2f72..9903561911 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -204,13 +204,18 @@ OMW::Engine::~Engine() void OMW::Engine::loadBSA() { const Files::MultiDirCollection& bsa = mFileCollections.getCollection (".bsa"); - std::string dataDirectory; + for (Files::MultiDirCollection::TIter iter(bsa.begin()); iter!=bsa.end(); ++iter) { std::cout << "Adding " << iter->second.string() << std::endl; Bsa::addBSA(iter->second.string()); + } - dataDirectory = iter->second.parent_path().string(); + const Files::PathContainer& dataDirs = mFileCollections.getPaths(); + std::string dataDirectory; + for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) + { + dataDirectory = iter->string(); std::cout << "Data dir " << dataDirectory << std::endl; Bsa::addDir(dataDirectory, mFSStrict); } diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 64dc979803..36f4b423ce 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -52,8 +52,15 @@ private: bool comparePortion(const std::string& file1, const std::string& file2, int start, int size) const { + return lexicographical_compare(file1.substr(start,size), file2.substr(start,size), boost::algorithm::is_iless()); + for(int i = start; i < start+size; i++) { + if (i >= file1.size()) + return true; + else if (i >= file2.size()) + return false; + char one = tolower(file1.at(i)); char two = tolower(file2.at(i)); if(one != two) @@ -83,7 +90,18 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { - copy = filename; + { + String passed = filename; + if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' + || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' + || filename.at(filename.length() - 1) == '|') + { + passed = filename.substr(0, filename.length() - 2); + } + if(filename.at(filename.length() - 2) == '>') + passed = filename.substr(0, filename.length() - 6); + copy = passed; + } std::replace(copy.begin(), copy.end(), '\\', '/'); diff --git a/components/files/collections.cpp b/components/files/collections.cpp index 424b558e60..50340dca4d 100644 --- a/components/files/collections.cpp +++ b/components/files/collections.cpp @@ -30,4 +30,9 @@ namespace Files return iter->second; } + + const Files::PathContainer& Collections::getPaths() const + { + return mDirectories; + } } diff --git a/components/files/collections.hpp b/components/files/collections.hpp index 1ddca9a5b7..70aaec55e3 100644 --- a/components/files/collections.hpp +++ b/components/files/collections.hpp @@ -21,6 +21,8 @@ namespace Files /// leading dot and must be all lower-case. const MultiDirCollection& getCollection(const std::string& extension) const; + const Files::PathContainer& getPaths() const; + private: typedef std::map MultiDirCollectionContainer; Files::PathContainer mDirectories; diff --git a/components/nifogre/ogre_nif_loader.cpp b/components/nifogre/ogre_nif_loader.cpp index f943231d0d..2ab6ae6211 100644 --- a/components/nifogre/ogre_nif_loader.cpp +++ b/components/nifogre/ogre_nif_loader.cpp @@ -1368,7 +1368,7 @@ void NIFLoader::loadResource(Resource *resource) if (!vfs->isFile(resourceName)) { - warn("File not found."); + warn("File "+resourceName+" not found."); return; } From d2f8539a4283bfd10c5a2639a64558d763adfd11 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 14:50:39 +0200 Subject: [PATCH 26/85] Forgot to remove some old code that didn't do anything --- components/bsa/bsa_archive.cpp | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 36f4b423ce..eb392757b8 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -50,31 +50,12 @@ struct pathComparer private: int m_start, m_size; - bool comparePortion(const std::string& file1, const std::string& file2, int start, int size) const - { - return lexicographical_compare(file1.substr(start,size), file2.substr(start,size), boost::algorithm::is_iless()); - - for(int i = start; i < start+size; i++) - { - if (i >= file1.size()) - return true; - else if (i >= file2.size()) - return false; - - char one = tolower(file1.at(i)); - char two = tolower(file2.at(i)); - if(one != two) - return (one < two); - } - return false; - } - public: pathComparer(int start, int size) : m_start(start), m_size(size) { } bool operator() (const std::string& first, const std::string& other) { - return comparePortion(first, other, m_start, m_size); + return lexicographical_compare(first.substr(m_start,m_size), other.substr(m_start,m_size), boost::algorithm::is_iless()); } }; From de102cd27465c22f32a6e16277c45a850d13e34d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 06:28:40 -0700 Subject: [PATCH 27/85] Simplify broken OpenAL workaround --- apps/openmw/mwsound/openal_output.cpp | 30 +++++---------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 0417596834..13ea49481d 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -442,33 +442,13 @@ void OpenAL_Output::init(const std::string &devname) { ALCuint maxtotal = std::min(maxmono+maxstereo, 256); if (maxtotal == 0) // workaround for broken implementations - { maxtotal = 256; - bool stop = false; - for(size_t i = 0;i < maxtotal && !stop;i++) // generate source until error returned - { - ALuint src = 0; - alGenSources(1, &src); - ALenum err = alGetError(); - if(err != AL_NO_ERROR) - { - stop = true; - } - else - { - mFreeSources.push_back(src); - } - } - } - else // normal case + for(size_t i = 0;i < maxtotal;i++) { - for(size_t i = 0;i < maxtotal;i++) - { - ALuint src = 0; - alGenSources(1, &src); - throwALerror(); - mFreeSources.push_back(src); - } + ALuint src = 0; + alGenSources(1, &src); + throwALerror(); + mFreeSources.push_back(src); } } catch(std::exception &e) From 71d9d7e94331df0ab05709cb00230b23fb836357 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 06:41:06 -0700 Subject: [PATCH 28/85] Store the current sound position with the Sound object --- apps/openmw/mwsound/sound.hpp | 6 +++++- apps/openmw/mwsound/soundmanager.cpp | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 2cbd48d961..f1b09875cd 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -1,6 +1,8 @@ #ifndef GAME_SOUND_SOUND_H #define GAME_SOUND_SOUND_H +#include + namespace MWSound { class Sound @@ -11,6 +13,7 @@ namespace MWSound Sound(const Sound &rhs); protected: + Ogre::Vector3 mPos; float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ float mBaseVolume; float mMinDistance; @@ -21,7 +24,8 @@ namespace MWSound virtual bool isPlaying() = 0; virtual void setVolume(float volume) = 0; - Sound() : mVolume(1.0f) + Sound() : mPos(0.0f, 0.0f, 0.0f) + , mVolume(1.0f) , mBaseVolume(1.0f) , mMinDistance(20.0f) /* 1 * min_range_scale */ , mMaxDistance(12750.0f) /* 255 * max_range_scale */ diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index f072e084db..d2e52043ab 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -187,6 +187,7 @@ namespace MWSound SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, 20.0f, 12750.0f, false); + sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); sound->mBaseVolume = basevol; mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); @@ -241,6 +242,7 @@ namespace MWSound const ESM::Position &pos = ptr.getCellRef().pos; sound = mOutput->playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop); + sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -330,7 +332,10 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(snditer->second.first == ptr) + { snditer->first->update(pos.pos); + snditer->first->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + } snditer++; } } From 479df78ea185bd56b8fe4f214beb1c1a8bc034cd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 07:01:37 -0700 Subject: [PATCH 29/85] Update the actual sound position after the listener --- apps/openmw/mwsound/openal_output.cpp | 12 ++++++------ apps/openmw/mwsound/sound.hpp | 2 +- apps/openmw/mwsound/soundmanager.cpp | 9 +++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 13ea49481d..ad0f7603ba 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -90,7 +90,7 @@ public: virtual void stop(); virtual bool isPlaying(); virtual void setVolume(float volume); - virtual void update(const float *pos); + virtual void update(); void play(); bool process(); @@ -262,9 +262,9 @@ void OpenAL_SoundStream::setVolume(float volume) mVolume = volume; } -void OpenAL_SoundStream::update(const float *pos) +void OpenAL_SoundStream::update() { - alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); @@ -340,7 +340,7 @@ public: virtual void stop(); virtual bool isPlaying(); virtual void setVolume(float volume); - virtual void update(const float *pos); + virtual void update(); }; OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) @@ -379,9 +379,9 @@ void OpenAL_Sound::setVolume(float volume) mVolume = volume; } -void OpenAL_Sound::update(const float *pos) +void OpenAL_Sound::update() { - alSource3f(mSource, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); throwALerror(); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index f1b09875cd..521d8dbca6 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -7,7 +7,7 @@ namespace MWSound { class Sound { - virtual void update(const float *pos) = 0; + virtual void update() = 0; Sound& operator=(const Sound &rhs); Sound(const Sound &rhs); diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index d2e52043ab..da7cb9d3b3 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -328,14 +328,12 @@ namespace MWSound void SoundManager::updateObject(MWWorld::Ptr ptr) { const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) { if(snditer->second.first == ptr) - { - snditer->first->update(pos.pos); - snditer->first->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); - } + snditer->first->mPos = objpos; snditer++; } } @@ -428,7 +426,10 @@ namespace MWSound if(!snditer->first->isPlaying()) mActiveSounds.erase(snditer++); else + { + snditer->first->update(); snditer++; + } } } From a4576f043d110479a236f1c91659298582839c11 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 30 Mar 2012 16:02:41 +0200 Subject: [PATCH 30/85] fixed the physics debug rendering now activates/deactivates properly --- apps/openmw/mwrender/debugging.cpp | 6 +----- libs/openengine/bullet/physic.cpp | 10 +++++++++- libs/openengine/bullet/physic.hpp | 3 +++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 60b299acd1..4221852730 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -27,11 +27,7 @@ bool Debugging::toggleRenderMode (int mode){ switch (mode) { case MWWorld::World::Render_CollisionDebug: - - // TODO use a proper function instead of accessing the member variable - // directly. - eng->setDebugRenderingMode (!eng->isDebugCreated); - return eng->isDebugCreated; + return eng->toggleDebugRendering(); } return false; diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp index 8b9f3dfecb..cc1f907a01 100644 --- a/libs/openengine/bullet/physic.cpp +++ b/libs/openengine/bullet/physic.cpp @@ -151,7 +151,8 @@ namespace Physic - PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) + PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) : + mDebugActive(0) { // Set up the collision configuration and dispatcher collisionConfiguration = new btDefaultCollisionConfiguration(); @@ -203,6 +204,13 @@ namespace Physic createDebugRendering(); } mDebugDrawer->setDebugMode(mode); + mDebugActive = mode; + } + + bool PhysicEngine::toggleDebugRendering() + { + setDebugRenderingMode(!mDebugActive); + return mDebugActive; } PhysicEngine::~PhysicEngine() diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 57ffe91305..16dac96f4c 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -199,6 +199,8 @@ namespace Physic */ void setDebugRenderingMode(int mode); + bool toggleDebugRendering(); + /** * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). */ @@ -230,6 +232,7 @@ namespace Physic //debug rendering BtOgre::DebugDrawer* mDebugDrawer; bool isDebugCreated; + bool mDebugActive; }; }} From fad27d99e66c6892f6e09d03691246ce833b3346 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 07:10:34 -0700 Subject: [PATCH 31/85] Update the actual sound volume with the position --- apps/openmw/mwsound/openal_output.cpp | 18 ++---------------- apps/openmw/mwsound/sound.hpp | 2 +- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ad0f7603ba..d8945cf52b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -89,7 +89,6 @@ public: virtual void stop(); virtual bool isPlaying(); - virtual void setVolume(float volume); virtual void update(); void play(); @@ -255,15 +254,9 @@ bool OpenAL_SoundStream::isPlaying() return !mIsFinished; } -void OpenAL_SoundStream::setVolume(float volume) -{ - alSourcef(mSource, AL_GAIN, volume*mBaseVolume); - throwALerror(); - mVolume = volume; -} - void OpenAL_SoundStream::update() { + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -339,7 +332,6 @@ public: virtual void stop(); virtual bool isPlaying(); - virtual void setVolume(float volume); virtual void update(); }; @@ -372,15 +364,9 @@ bool OpenAL_Sound::isPlaying() return state==AL_PLAYING; } -void OpenAL_Sound::setVolume(float volume) -{ - alSourcef(mSource, AL_GAIN, volume*mBaseVolume); - throwALerror(); - mVolume = volume; -} - void OpenAL_Sound::update() { + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index 521d8dbca6..d2c2d52091 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -22,7 +22,7 @@ namespace MWSound public: virtual void stop() = 0; virtual bool isPlaying() = 0; - virtual void setVolume(float volume) = 0; + void setVolume(float volume) { mVolume = volume; } Sound() : mPos(0.0f, 0.0f, 0.0f) , mVolume(1.0f) From 1ee8b963d06a17f58f4d28fac02ac3fefcab79bb Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 07:30:17 -0700 Subject: [PATCH 32/85] Store the sound listener position as well --- apps/openmw/mwsound/openal_output.cpp | 3 ++- apps/openmw/mwsound/sound_output.hpp | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index d8945cf52b..8d574b2f2b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -760,8 +760,9 @@ void OpenAL_Output::updateListener(const float *pos, const float *atdir, const f atdir[0], atdir[2], -atdir[1], updir[0], updir[2], -updir[1] }; + mPos = Ogre::Vector3(pos[0], pos[1], pos[2]); - alListener3f(AL_POSITION, pos[0], pos[2], -pos[1]); + alListener3f(AL_POSITION, mPos[0], mPos[2], -mPos[1]); alListenerfv(AL_ORIENTATION, orient); throwALerror(); } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 794383591b..3c1a3d3127 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include "soundmanager.hpp" #include "../mwworld/ptr.hpp" @@ -34,7 +36,13 @@ namespace MWSound Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); - Sound_Output(SoundManager &mgr) : mManager(mgr) { } + protected: + Ogre::Vector3 mPos; + + Sound_Output(SoundManager &mgr) + : mManager(mgr) + , mPos(0.0f, 0.0f, 0.0f) + { } public: virtual ~Sound_Output() { } From 8ac9dd8e70116eb8adf3f1f34a50a4d9b776fd32 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 16:59:19 +0200 Subject: [PATCH 33/85] Always use the same type of slashes --- components/bsa/bsa_archive.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index eb392757b8..e33f0ba49d 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -104,13 +104,10 @@ class DirArchive: public Ogre::FileSystemArchive std::vector current; { std::map,ciLessBoost>::const_iterator found = m.find(folder); - if (found == m.end()) - { - std::replace(folder.begin(), folder.end(), '/', '\\'); - found = m.find(folder); - } - if (found != m.end()) + if (found == m.end()) + return false; + else current = found->second; } @@ -134,16 +131,14 @@ class DirArchive: public Ogre::FileSystemArchive //need to cut off first boost::filesystem::directory_iterator dir_iter(d), dir_end; std::vector filesind; - boost::filesystem::path f; for(;dir_iter != dir_end; dir_iter++) { if(boost::filesystem::is_directory(*dir_iter)) populateMap(*dir_iter); else { - - f = *dir_iter; - std::string s = f.string(); + std::string s = dir_iter->path().string(); + std::replace(s.begin(), s.end(), '\\', '/'); std::string small; if(cutoff < s.size()) @@ -155,14 +150,16 @@ class DirArchive: public Ogre::FileSystemArchive } } std::sort(filesind.begin(), filesind.end(), ciLessBoost()); + std::string small; std::string original = d.string(); + std::replace(original.begin(), original.end(), '\\', '/'); if(cutoff < original.size()) small = original.substr(cutoff, original.size() - cutoff); else small = original.substr(cutoff - 1, original.size() - cutoff); - m[small] = filesind; + m[small] = filesind; } bool isCaseSensitive() const { return fsstrict; } From fc4e4dc336feb15584875f651e88f1cd0936a8e6 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 19:05:58 +0200 Subject: [PATCH 34/85] Case sensitiviy? --- components/bsa/bsa_archive.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index e33f0ba49d..bd61e03395 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -112,7 +112,13 @@ class DirArchive: public Ogre::FileSystemArchive } pathComparer comp(delimiter, copy.size() - delimiter-1); - return std::binary_search(current.begin(), current.end(), copy, comp); + std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); + if (find != current.end() && !comp(copy, current.front())) + { + copy = *find; + return true; + } + return false; } public: From fefc8f86ab05f48cd5253a4939d96e9354c3fbd3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 11:11:07 -0700 Subject: [PATCH 35/85] Remove the unused streamSound3D method --- apps/openmw/mwsound/openal_output.cpp | 44 --------------------------- apps/openmw/mwsound/openal_output.hpp | 3 -- apps/openmw/mwsound/sound_output.hpp | 2 -- 3 files changed, 49 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 8d574b2f2b..da1de3d147 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -709,50 +709,6 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa return sound; } -SoundPtr OpenAL_Output::streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) -{ - throwALerror(); - - boost::shared_ptr sound; - ALuint src; - - if(mFreeSources.empty()) - fail("No free sources"); - src = mFreeSources.front(); - mFreeSources.pop_front(); - - try - { - DecoderPtr decoder = mManager.getDecoder(); - decoder->open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); - } - catch(std::exception &e) - { - mFreeSources.push_back(src); - throw; - } - - alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); - alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); - alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); - - alSourcef(src, AL_REFERENCE_DISTANCE, min); - alSourcef(src, AL_MAX_DISTANCE, max); - alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - - alSourcef(src, AL_GAIN, volume); - alSourcef(src, AL_PITCH, pitch); - - alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, AL_FALSE); - throwALerror(); - - sound->play(); - return sound; -} - void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index d288a62f39..f37f72cabd 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -43,10 +43,7 @@ namespace MWSound virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop); virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop); - virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); - virtual SoundPtr streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max); virtual void updateListener(const float *pos, const float *atdir, const float *updir); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 3c1a3d3127..e54f9016da 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -28,8 +28,6 @@ namespace MWSound virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, float min, float max, bool loop) = 0; virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; - virtual SoundPtr streamSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max) = 0; virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; From fc167dbc83b6312433d1469ec8222f22ac47e10b Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 30 Mar 2012 11:42:11 -0700 Subject: [PATCH 36/85] Pass Ogre Vector3s to playSound3D and updateListener --- apps/openmw/mwsound/openal_output.cpp | 16 ++++++++-------- apps/openmw/mwsound/openal_output.hpp | 6 +++--- apps/openmw/mwsound/sound_output.hpp | 6 +++--- apps/openmw/mwsound/soundmanager.cpp | 16 +++++++++------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index da1de3d147..9fda1470c6 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -616,7 +616,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float return sound; } -SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const float *pos, float volume, float pitch, +SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, bool loop) { throwALerror(); @@ -643,7 +643,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const float *pos, throw; } - alSource3f(src, AL_POSITION, pos[0], pos[2], -pos[1]); + alSource3f(src, AL_POSITION, pos.x, pos.z, -pos.y); alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -710,15 +710,15 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa } -void OpenAL_Output::updateListener(const float *pos, const float *atdir, const float *updir) +void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) { - float orient[6] = { - atdir[0], atdir[2], -atdir[1], - updir[0], updir[2], -updir[1] + ALfloat orient[6] = { + atdir.x, atdir.z, -atdir.y, + updir.x, updir.z, -updir.y }; - mPos = Ogre::Vector3(pos[0], pos[1], pos[2]); + mPos = pos; - alListener3f(AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); alListenerfv(AL_ORIENTATION, orient); throwALerror(); } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index f37f72cabd..f4d4e90afd 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -41,11 +41,11 @@ namespace MWSound virtual void deinit(); virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop); - virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop); + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, bool loop); virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); - virtual void updateListener(const float *pos, const float *atdir, const float *updir); + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir); OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index e54f9016da..ed3dda2f34 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -25,11 +25,11 @@ namespace MWSound virtual void deinit() = 0; virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; - virtual SoundPtr playSound3D(const std::string &fname, const float *pos, float volume, float pitch, - float min, float max, bool loop) = 0; + virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, + float volume, float pitch, float min, float max, bool loop) = 0; virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; - virtual void updateListener(const float *pos, const float *atdir, const float *updir) = 0; + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) = 0; Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index da7cb9d3b3..005df3e7ef 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -184,10 +184,11 @@ namespace MWSound float basevol = 1.0f; /* TODO: volume settings */ std::string filePath = "Sound/"+filename; const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f, + SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, 20.0f, 12750.0f, false); - sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + sound->mPos = objpos; sound->mBaseVolume = basevol; mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); @@ -240,9 +241,10 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - sound = mOutput->playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop); - sound->mPos = Ogre::Vector3(pos.pos[0], pos.pos[1], pos.pos[2]); + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, loop); + sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -414,9 +416,9 @@ namespace MWSound // The output handler is expecting vectors oriented like the game // (that is, -Z goes down, +Y goes forward), but that's not what we // get from Ogre's camera, so we have to convert. - float pos[3] = { nPos[0], -nPos[2], nPos[1] }; - float at[3] = { nDir[0], -nDir[2], nDir[1] }; - float up[3] = { nUp[0], -nUp[2], nUp[1] }; + const Ogre::Vector3 pos(nPos[0], -nPos[2], nPos[1]); + const Ogre::Vector3 at(nDir[0], -nDir[2], nDir[1]); + const Ogre::Vector3 up(nUp[0], -nUp[2], nUp[1]); mOutput->updateListener(pos, at, up); // Check if any sounds are finished playing, and trash them From fbd626baf69da248f64e0545d635799469a7a09e Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 30 Mar 2012 20:59:44 +0200 Subject: [PATCH 37/85] mwiniimporter --- CMakeLists.txt | 7 +++++- apps/mwiniimporter/CMakeLists.txt | 21 +++++++++++++++++ apps/mwiniimporter/importer.cpp | 6 +++++ apps/mwiniimporter/importer.hpp | 17 ++++++++++++++ apps/mwiniimporter/main.cpp | 39 +++++++++++++++++++++++++++++++ apps/mwiniimporter/main.hpp | 11 +++++++++ 6 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 apps/mwiniimporter/CMakeLists.txt create mode 100644 apps/mwiniimporter/importer.cpp create mode 100644 apps/mwiniimporter/importer.hpp create mode 100644 apps/mwiniimporter/main.cpp create mode 100644 apps/mwiniimporter/main.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f007dbccf..552a6997a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,7 +305,7 @@ if(DPKG_PROGRAM) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "nvidia-cg-toolkit (>= 2.1), libboost-filesystem1.46.1 (>= 1.46.1), libboost-program-options1.46.1 (>= 1.46.1), libboost-system1.46.1 (>= 1.46.1), libboost-thread1.46.1 (>= 1.46.1), libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libois-1.3.0 (>= 1.3.0), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") @@ -391,6 +391,11 @@ if (BUILD_LAUNCHER) add_subdirectory( apps/launcher ) endif() +option(BUILD_MWINIIMPORTER "build MWiniImporter inspector" ON) +if (BUILD_MWINIIMPORTER) + add_subdirectory( apps/mwiniimporter ) +endif() + if (WIN32) if (MSVC) if (USE_DEBUG_CONSOLE) diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt new file mode 100644 index 0000000000..1d7b7b624c --- /dev/null +++ b/apps/mwiniimporter/CMakeLists.txt @@ -0,0 +1,21 @@ +set(MWINIIMPORT + main.cpp + importer.cpp +) + +set(MWINIIMPORT_HEADER + main.hpp + importer.hpp +) + +source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) + +add_executable(mwiniimport + ${MWINIIMPORT} +) + +target_link_libraries(mwiniimport + ${Boost_LIBRARIES} + components +) + diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp new file mode 100644 index 0000000000..225b1667c4 --- /dev/null +++ b/apps/mwiniimporter/importer.cpp @@ -0,0 +1,6 @@ +#include "importer.hpp" + +void MwIniImporter::test() { + +} + diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp new file mode 100644 index 0000000000..026ed55bf9 --- /dev/null +++ b/apps/mwiniimporter/importer.hpp @@ -0,0 +1,17 @@ +#ifndef MWINIIMPORTER_IMPORTER +#define MWINIIMPORTER_IMPORTER 1 + +#include + +class MwIniImporter { + + public: + void test(); + + private: + + +}; + + +#endif diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp new file mode 100644 index 0000000000..46cb37e7f4 --- /dev/null +++ b/apps/mwiniimporter/main.cpp @@ -0,0 +1,39 @@ +#include "main.hpp" +#include "importer.hpp" + +int main(int argc, char *argv[]) { + + bpo::options_description desc("Syntax: mwiniimporter \nAllowed options"); + desc.add_options() + ("help,h", "produce help message") + ("in,i", bpo::value()->required(), "morrowind.ini input file") + ("out,o", bpo::value()->required(), "openmw.cfg output file") + ; + + bpo::variables_map vm; + try { + bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm); + bpo::notify(vm); + + } + catch(std::exception& e) { + std::cout << "Error:" << e.what() << std::endl; + return -1; + } + catch(...) { + std::cout << "Error" << std::endl; + return -1; + } + + if(vm.count("help")) { + std::cout << desc; + return 0; + } + + std::cout << "in:" << vm["in"].as() << std::endl; + + MwIniImporter importer; + importer.test(); + + return 0; +} diff --git a/apps/mwiniimporter/main.hpp b/apps/mwiniimporter/main.hpp new file mode 100644 index 0000000000..f93de7b078 --- /dev/null +++ b/apps/mwiniimporter/main.hpp @@ -0,0 +1,11 @@ +#ifndef MWINIIMPORTER_MAIN +#define MWINIIMPORTER_MAIN 1 + +#include +#include +#include + +namespace bpo = boost::program_options; + + +#endif From c160bc708091039cccdea53bc584704713193852 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 30 Mar 2012 22:58:54 +0200 Subject: [PATCH 38/85] works, sort of --- apps/mwiniimporter/CMakeLists.txt | 1 - apps/mwiniimporter/importer.cpp | 94 ++++++++++++++++++++++++++++++- apps/mwiniimporter/importer.hpp | 18 +++++- apps/mwiniimporter/main.cpp | 58 +++++++++++++++---- apps/mwiniimporter/main.hpp | 11 ---- 5 files changed, 155 insertions(+), 27 deletions(-) delete mode 100644 apps/mwiniimporter/main.hpp diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 1d7b7b624c..2a8c0f5fea 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -4,7 +4,6 @@ set(MWINIIMPORT ) set(MWINIIMPORT_HEADER - main.hpp importer.hpp ) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 225b1667c4..d0cfe4a04e 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -1,6 +1,96 @@ #include "importer.hpp" +#include +#include +#include -void MwIniImporter::test() { - +void MwIniImporter::setVerbose(bool verbose) { + mVerbose = verbose; } +strmap MwIniImporter::loadIniFile(std::string filename) { + std::cout << "load ini file: " << filename << std::endl; + + std::map map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + // ignore sections for now + if(line.empty() || line[0] == ';' || line[0] == '[') { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + throw IniParseException(); + } + + map.insert(std::pair( + line.substr(0,pos), line.substr(pos+1) + )); + } + + return map; +} + +strmap MwIniImporter::loadCfgFile(std::string filename) { + std::cout << "load cfg file: " << filename << std::endl; + + std::map map; + boost::iostreams::streamfile(filename.c_str()); + + std::string line; + while (std::getline(file, line)) { + + if(line[0] == '[') { // section + continue; // ignore for now + } + + // we cant say comment by only looking at first char anymore + int comment_pos = line.find("#"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + + int pos = line.find("="); + if(pos < 1) { + throw IniParseException(); + } + + map.insert(std::pair( + line.substr(0,pos), line.substr(pos+1) + )); + } + + return map; +} + +void MwIniImporter::merge(strmap &cfg, strmap &ini) { + strmap::iterator ini_it; + for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + ini_it = ini.find(it->first); + + // found a key in both files + if(ini_it != ini.end()) { + cfg.erase(it); + cfg.insert(std::pair( + ini_it->first, ini_it->second + )); + } + } +} + +void MwIniImporter::writeToFile(std::string file, strmap &cfg) { + boost::iostreams::stream out(file); + + for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + out << (it->first) << "=" << (it->second) << std::endl; + } +} + + diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 026ed55bf9..1933830f21 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -2,14 +2,28 @@ #define MWINIIMPORTER_IMPORTER 1 #include +#include +#include + +typedef std::map strmap; + +class IniParseException : public std::exception { + virtual const char* what() const throw() { + return "unexpected end of line"; + } +}; class MwIniImporter { public: - void test(); + void setVerbose(bool verbose); + strmap loadIniFile(std::string filename); + strmap loadCfgFile(std::string filename); + void merge(strmap &cfg, strmap &ini); + void writeToFile(std::string file, strmap &cfg); private: - + bool mVerbose; }; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 46cb37e7f4..fa7a5c512f 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -1,39 +1,75 @@ -#include "main.hpp" #include "importer.hpp" +#include +#include +#include +#include + +namespace bpo = boost::program_options; + int main(int argc, char *argv[]) { bpo::options_description desc("Syntax: mwiniimporter \nAllowed options"); desc.add_options() ("help,h", "produce help message") - ("in,i", bpo::value()->required(), "morrowind.ini input file") - ("out,o", bpo::value()->required(), "openmw.cfg output file") + ("verbose,v", "verbose output") + ("ini,i", bpo::value()->required(), "morrowind.ini file") + ("cfg,c", bpo::value()->required(), "openmw.cfg file") + ("output,o", bpo::value()->default_value(""), "openmw.cfg file") ; bpo::variables_map vm; try { bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm); + + // parse help before calling notify because we dont want it to throw an error if help is set + if(vm.count("help")) { + std::cout << desc; + return 0; + } + bpo::notify(vm); } catch(std::exception& e) { - std::cout << "Error:" << e.what() << std::endl; + std::cerr << "Error:" << e.what() << std::endl; return -1; } catch(...) { - std::cout << "Error" << std::endl; - return -1; + std::cerr << "Error" << std::endl; + return -2; } - if(vm.count("help")) { - std::cout << desc; - return 0; + std::string iniFile = vm["ini"].as(); + std::string cfgFile = vm["cfg"].as(); + + // if no output is given, write back to cfg file + std::string outputFile(vm["output"].as()); + if(vm["output"].defaulted()) { + outputFile = vm["cfg"].as(); } - std::cout << "in:" << vm["in"].as() << std::endl; + if(!boost::filesystem::exists(iniFile)) { + std::cerr << "ini file does not exist" << std::endl; + return -3; + } + if(!boost::filesystem::exists(cfgFile)) { + std::cerr << "cfg file does not exist" << std::endl; + return -4; + } MwIniImporter importer; - importer.test(); + importer.setVerbose(vm.count("verbose")); + + std::mapini = importer.loadIniFile(iniFile); + std::mapcfg = importer.loadCfgFile(cfgFile); + + importer.merge(cfg, ini); + + std::cout << "write to: " << outputFile << std::endl; + importer.writeToFile(outputFile, cfg); return 0; } + + diff --git a/apps/mwiniimporter/main.hpp b/apps/mwiniimporter/main.hpp deleted file mode 100644 index f93de7b078..0000000000 --- a/apps/mwiniimporter/main.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef MWINIIMPORTER_MAIN -#define MWINIIMPORTER_MAIN 1 - -#include -#include -#include - -namespace bpo = boost::program_options; - - -#endif From 849c3a9bececfdeba8495f09dc057f62236027e8 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 30 Mar 2012 23:12:52 +0200 Subject: [PATCH 39/85] add the section to the ini-keys --- apps/mwiniimporter/importer.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index d0cfe4a04e..f662f42d64 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -10,6 +10,7 @@ void MwIniImporter::setVerbose(bool verbose) { strmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; + std::string section(""); std::map map; boost::iostreams::streamfile(filename.c_str()); @@ -17,17 +18,25 @@ strmap MwIniImporter::loadIniFile(std::string filename) { while (std::getline(file, line)) { // ignore sections for now - if(line.empty() || line[0] == ';' || line[0] == '[') { + if(line.empty() || line[0] == ';') { continue; } + if(line[0] == '[') { + if(line.length() > 2) { + section = line.substr(1, line.length()-3); + continue; + } + throw IniParseException(); + } + int pos = line.find("="); if(pos < 1) { throw IniParseException(); } map.insert(std::pair( - line.substr(0,pos), line.substr(pos+1) + section + " " + line.substr(0,pos), line.substr(pos+1) )); } From 8d9100c77b7fd67df4c18a1642beffcf3bc68b72 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 30 Mar 2012 23:29:58 +0200 Subject: [PATCH 40/85] Debug output --- components/bsa/bsa_archive.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index bd61e03395..9d7212a5c4 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -101,12 +101,17 @@ class DirArchive: public Ogre::FileSystemArchive delimiter = lastSlash+1; } + std::cout << "Finding: " << copy; + std::vector current; { std::map,ciLessBoost>::const_iterator found = m.find(folder); if (found == m.end()) + { + std::cout << " failed, couldn't find folder." << std::endl; return false; + } else current = found->second; } @@ -115,9 +120,18 @@ class DirArchive: public Ogre::FileSystemArchive std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); if (find != current.end() && !comp(copy, current.front())) { + std::cout << " found"; + if (copy != *find) + if (lexicographical_compare(copy, *find, boost::algorithm::is_iless())) + std::cout << " case folded to " << *find << std::endl; + else + std::cout << " as different file " << *find << std::endl; + copy = *find; return true; } + + std::cout << " failed, couldn't find file." << std::endl; return false; } From 8e07b7e05002e246d785b3c6d7b35ace6bdb68b1 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 00:54:49 +0200 Subject: [PATCH 41/85] Better (less) debug output --- components/bsa/bsa_archive.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 9d7212a5c4..6501271597 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -101,37 +101,34 @@ class DirArchive: public Ogre::FileSystemArchive delimiter = lastSlash+1; } - std::cout << "Finding: " << copy; - std::vector current; { std::map,ciLessBoost>::const_iterator found = m.find(folder); if (found == m.end()) { - std::cout << " failed, couldn't find folder." << std::endl; return false; } else current = found->second; } + std::cout << "Finding: " << copy; + pathComparer comp(delimiter, copy.size() - delimiter-1); std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); if (find != current.end() && !comp(copy, current.front())) { std::cout << " found"; - if (copy != *find) - if (lexicographical_compare(copy, *find, boost::algorithm::is_iless())) - std::cout << " case folded to " << *find << std::endl; - else - std::cout << " as different file " << *find << std::endl; + if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iless())) + std::cout << ", as different file " << *find; + std::cout << "." << std::endl; copy = *find; return true; } - std::cout << " failed, couldn't find file." << std::endl; + std::cout << " failed." << std::endl; return false; } From 5adeee20fd893f1c7b04206365b6b06a8bacaa9c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 00:57:29 -0700 Subject: [PATCH 42/85] Mute sounds that go outside the max distance --- apps/openmw/mwsound/openal_output.cpp | 34 +++++++++++++++++---------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 9fda1470c6..1b90c62fa1 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -77,6 +77,7 @@ class OpenAL_SoundStream : public Sound ALuint mBufferSize; DecoderPtr mDecoder; + bool mIs3D; volatile bool mIsFinished; @@ -84,7 +85,7 @@ class OpenAL_SoundStream : public Sound OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D); virtual ~OpenAL_SoundStream(); virtual void stop(); @@ -164,8 +165,8 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) - : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D) + : mOutput(output), mSource(src), mDecoder(decoder), mIs3D(is3D), mIsFinished(true) { throwALerror(); @@ -256,7 +257,10 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + alSourcef(mSource, AL_GAIN, 0.0f); + else + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -323,11 +327,13 @@ class OpenAL_Sound : public Sound ALuint mSource; ALuint mBuffer; + bool mIs3D; + OpenAL_Sound(const OpenAL_Sound &rhs); OpenAL_Sound& operator=(const OpenAL_Sound &rhs); public: - OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf); + OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D); virtual ~OpenAL_Sound(); virtual void stop(); @@ -335,8 +341,8 @@ public: virtual void update(); }; -OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) - : mOutput(output), mSource(src), mBuffer(buf) +OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D) + : mOutput(output), mSource(src), mBuffer(buf), mIs3D(is3D) { } OpenAL_Sound::~OpenAL_Sound() @@ -366,7 +372,10 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + alSourcef(mSource, AL_GAIN, 0.0f); + else + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -583,7 +592,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound(*this, src, buf, false)); } catch(std::exception &e) { @@ -632,7 +641,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf)); + sound.reset(new OpenAL_Sound(*this, src, buf, true)); } catch(std::exception &e) { @@ -651,7 +660,8 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector alSourcef(src, AL_MAX_DISTANCE, max); alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); - alSourcef(src, AL_GAIN, volume); + alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? + 0.0f : volume); alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); @@ -682,7 +692,7 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa { DecoderPtr decoder = mManager.getDecoder(); decoder->open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder)); + sound.reset(new OpenAL_SoundStream(*this, src, decoder, false)); } catch(std::exception &e) { From b01289128bb50e6216799cbb0e6427bb0df369d9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 01:15:27 -0700 Subject: [PATCH 43/85] Split OpenAL_Sound into separate classes for 2D and 3D sounds --- apps/openmw/mwsound/openal_output.cpp | 57 ++++++++++++++++++--------- apps/openmw/mwsound/openal_output.hpp | 1 + 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 1b90c62fa1..3ed86cf2bc 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -77,7 +77,6 @@ class OpenAL_SoundStream : public Sound ALuint mBufferSize; DecoderPtr mDecoder; - bool mIs3D; volatile bool mIsFinished; @@ -85,7 +84,7 @@ class OpenAL_SoundStream : public Sound OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs); public: - OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D); + OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder); virtual ~OpenAL_SoundStream(); virtual void stop(); @@ -165,8 +164,8 @@ private: }; -OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder, bool is3D) - : mOutput(output), mSource(src), mDecoder(decoder), mIs3D(is3D), mIsFinished(true) +OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder) + : mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true) { throwALerror(); @@ -257,10 +256,7 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { - if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) - alSourcef(mSource, AL_GAIN, 0.0f); - else - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -318,22 +314,22 @@ bool OpenAL_SoundStream::process() } // -// A regular OpenAL sound +// A regular 2D OpenAL sound // class OpenAL_Sound : public Sound { +protected: OpenAL_Output &mOutput; ALuint mSource; ALuint mBuffer; - bool mIs3D; - +private: OpenAL_Sound(const OpenAL_Sound &rhs); OpenAL_Sound& operator=(const OpenAL_Sound &rhs); public: - OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D); + OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf); virtual ~OpenAL_Sound(); virtual void stop(); @@ -341,8 +337,24 @@ public: virtual void update(); }; -OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf, bool is3D) - : mOutput(output), mSource(src), mBuffer(buf), mIs3D(is3D) +// +// A regular 3D OpenAL sound +// +class OpenAL_Sound3D : public OpenAL_Sound +{ + OpenAL_Sound3D(const OpenAL_Sound &rhs); + OpenAL_Sound3D& operator=(const OpenAL_Sound &rhs); + +public: + OpenAL_Sound3D(OpenAL_Output &output, ALuint src, ALuint buf) + : OpenAL_Sound(output, src, buf) + { } + + virtual void update(); +}; + +OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf) + : mOutput(output), mSource(src), mBuffer(buf) { } OpenAL_Sound::~OpenAL_Sound() @@ -372,7 +384,16 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { - if(mIs3D && mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) + alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); + alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + throwALerror(); +} + +void OpenAL_Sound3D::update() +{ + if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) alSourcef(mSource, AL_GAIN, 0.0f); else alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); @@ -592,7 +613,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf, false)); + sound.reset(new OpenAL_Sound(*this, src, buf)); } catch(std::exception &e) { @@ -641,7 +662,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector try { buf = getBuffer(fname); - sound.reset(new OpenAL_Sound(*this, src, buf, true)); + sound.reset(new OpenAL_Sound3D(*this, src, buf)); } catch(std::exception &e) { @@ -692,7 +713,7 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa { DecoderPtr decoder = mManager.getDecoder(); decoder->open(fname); - sound.reset(new OpenAL_SoundStream(*this, src, decoder, false)); + sound.reset(new OpenAL_SoundStream(*this, src, decoder)); } catch(std::exception &e) { diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index f4d4e90afd..35966cc29b 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -57,6 +57,7 @@ namespace MWSound std::auto_ptr mStreamThread; friend class OpenAL_Sound; + friend class OpenAL_Sound3D; friend class OpenAL_SoundStream; friend class SoundManager; }; From 4e908aa0954801549c5d0f8c30bad057e65c5c9e Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 01:34:26 -0700 Subject: [PATCH 44/85] Add a method to set the sound's position --- apps/openmw/mwsound/sound.hpp | 1 + apps/openmw/mwsound/soundmanager.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index d2c2d52091..e23830cdb9 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -22,6 +22,7 @@ namespace MWSound public: virtual void stop() = 0; virtual bool isPlaying() = 0; + void setPosition(const Ogre::Vector3 &pos) { mPos = pos; } void setVolume(float volume) { mVolume = volume; } Sound() : mPos(0.0f, 0.0f, 0.0f) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 005df3e7ef..9c26f00846 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -335,7 +335,7 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(snditer->second.first == ptr) - snditer->first->mPos = objpos; + snditer->first->setPosition(objpos); snditer++; } } From 06a34b9e0d9edf3060690094ec8ed8a0c1194c77 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 10:35:08 +0200 Subject: [PATCH 45/85] Strange problems call for strange solutions. --- components/bsa/bsa_archive.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 6501271597..660b24d560 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -71,6 +71,9 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { + if (filename.find(".tga") != std::string::npos) + return false; + { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -120,8 +123,8 @@ class DirArchive: public Ogre::FileSystemArchive if (find != current.end() && !comp(copy, current.front())) { std::cout << " found"; - if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iless())) - std::cout << ", as different file " << *find; + if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iequal())) + std::cout << ", as different file " << *find; std::cout << "." << std::endl; copy = *find; From 28378c063b0b7601e2846d33963834473f35dad3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 02:22:23 -0700 Subject: [PATCH 46/85] Minor OpenAL_SoundStream cleanups --- apps/openmw/mwsound/openal_output.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3ed86cf2bc..c40b340c56 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -65,7 +65,7 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) class OpenAL_SoundStream : public Sound { static const ALuint sNumBuffers = 6; - static const ALfloat sBufferLength; + static const ALfloat sBufferLength = 0.125f; OpenAL_Output &mOutput; @@ -95,7 +95,6 @@ public: bool process(); }; -const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; // // A background streaming thread (keeps active streams processed) @@ -186,7 +185,6 @@ OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, Decode } catch(std::exception &e) { - mOutput.mFreeSources.push_back(mSource); alDeleteBuffers(sNumBuffers, mBuffers); alGetError(); throw; From 3b0dc408ae1866b5a0d702b8fce6e453eb697b47 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 11:29:24 +0200 Subject: [PATCH 47/85] Debug-b-gone --- components/bsa/bsa_archive.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 660b24d560..41bff7e40a 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -71,9 +71,6 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { - if (filename.find(".tga") != std::string::npos) - return false; - { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -116,22 +113,14 @@ class DirArchive: public Ogre::FileSystemArchive current = found->second; } - std::cout << "Finding: " << copy; - pathComparer comp(delimiter, copy.size() - delimiter-1); std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); if (find != current.end() && !comp(copy, current.front())) { - std::cout << " found"; - if (copy != *find && !lexicographical_compare(copy, *find, boost::algorithm::is_iequal())) - std::cout << ", as different file " << *find; - - std::cout << "." << std::endl; copy = *find; return true; } - std::cout << " failed." << std::endl; return false; } From 6eb3281c4c504dfc2d6398156ee5c449772325c8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 31 Mar 2012 11:36:51 +0200 Subject: [PATCH 48/85] boost fix --- apps/mwiniimporter/main.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index fa7a5c512f..059703ea89 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -13,23 +13,23 @@ int main(int argc, char *argv[]) { desc.add_options() ("help,h", "produce help message") ("verbose,v", "verbose output") - ("ini,i", bpo::value()->required(), "morrowind.ini file") - ("cfg,c", bpo::value()->required(), "openmw.cfg file") + ("ini,i", bpo::value(), "morrowind.ini file") + ("cfg,c", bpo::value(), "openmw.cfg file") ("output,o", bpo::value()->default_value(""), "openmw.cfg file") ; - + bpo::variables_map vm; try { bpo::store(boost::program_options::parse_command_line(argc, argv, desc), vm); - + // parse help before calling notify because we dont want it to throw an error if help is set if(vm.count("help")) { std::cout << desc; return 0; } - + bpo::notify(vm); - + } catch(std::exception& e) { std::cerr << "Error:" << e.what() << std::endl; @@ -39,16 +39,16 @@ int main(int argc, char *argv[]) { std::cerr << "Error" << std::endl; return -2; } - + std::string iniFile = vm["ini"].as(); std::string cfgFile = vm["cfg"].as(); - + // if no output is given, write back to cfg file std::string outputFile(vm["output"].as()); if(vm["output"].defaulted()) { outputFile = vm["cfg"].as(); } - + if(!boost::filesystem::exists(iniFile)) { std::cerr << "ini file does not exist" << std::endl; return -3; @@ -57,19 +57,17 @@ int main(int argc, char *argv[]) { std::cerr << "cfg file does not exist" << std::endl; return -4; } - + MwIniImporter importer; importer.setVerbose(vm.count("verbose")); - + std::mapini = importer.loadIniFile(iniFile); std::mapcfg = importer.loadCfgFile(cfgFile); - + importer.merge(cfg, ini); - + std::cout << "write to: " << outputFile << std::endl; importer.writeToFile(outputFile, cfg); - + return 0; } - - From 7541e08909ee3be2655831bdf0a680ea45112a82 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 02:48:14 -0700 Subject: [PATCH 49/85] Don't reset the sound Output device if init fails --- apps/openmw/mwsound/soundmanager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 9c26f00846..d537a2bbd4 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -62,7 +62,6 @@ namespace MWSound catch(std::exception &e) { std::cout <<"Sound init failed: "< Date: Sat, 31 Mar 2012 03:31:41 -0700 Subject: [PATCH 50/85] Finally "fix" --nosound Expect degraded performance with it. Looping sounds are constantly checked to see if they're playing, and try to play it again when it's not. --- apps/openmw/mwsound/openal_output.cpp | 41 ++++++++++++++++----------- apps/openmw/mwsound/soundmanager.cpp | 5 ++-- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index c40b340c56..6a4e3fe570 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -25,14 +25,20 @@ static void throwALCerror(ALCdevice *device) { ALCenum err = alcGetError(device); if(err != ALC_NO_ERROR) - fail(alcGetString(device, err)); + { + const ALCchar *errstring = alcGetString(device, err); + fail(errstring ? errstring : ""); + } } static void throwALerror() { ALenum err = alGetError(); if(err != AL_NO_ERROR) - fail(alGetString(err)); + { + const ALchar *errstring = alGetString(err); + fail(errstring ? errstring : ""); + } } @@ -424,8 +430,7 @@ std::vector OpenAL_Output::enumerate() void OpenAL_Output::init(const std::string &devname) { - if(mDevice || mContext) - fail("Device already open"); + deinit(); mDevice = alcOpenDevice(devname.c_str()); if(!mDevice) @@ -442,7 +447,12 @@ void OpenAL_Output::init(const std::string &devname) mContext = alcCreateContext(mDevice, NULL); if(!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE) + { + if(mContext) + alcDestroyContext(mContext); + mContext = 0; fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice))); + } alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); throwALerror(); @@ -598,8 +608,6 @@ void OpenAL_Output::bufferFinished(ALuint buf) SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) { - throwALerror(); - boost::shared_ptr sound; ALuint src=0, buf=0; @@ -647,8 +655,6 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, bool loop) { - throwALerror(); - boost::shared_ptr sound; ALuint src=0, buf=0; @@ -697,8 +703,6 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) { - throwALerror(); - boost::shared_ptr sound; ALuint src; @@ -741,15 +745,18 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) { - ALfloat orient[6] = { - atdir.x, atdir.z, -atdir.y, - updir.x, updir.z, -updir.y - }; mPos = pos; - alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); - alListenerfv(AL_ORIENTATION, orient); - throwALerror(); + if(mContext) + { + ALfloat orient[6] = { + atdir.x, atdir.z, -atdir.y, + updir.x, updir.z, -updir.y + }; + alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y); + alListenerfv(AL_ORIENTATION, orient); + throwALerror(); + } } diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index d537a2bbd4..534b5b6deb 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -41,6 +41,8 @@ namespace MWSound SoundManager::SoundManager(bool useSound, MWWorld::Environment& environment) : mResourceMgr(Ogre::ResourceGroupManager::getSingleton()) , mEnvironment(environment) + , mOutput(new DEFAULT_OUTPUT(*this)) + { if(!useSound) return; @@ -50,8 +52,6 @@ namespace MWSound try { - mOutput.reset(new DEFAULT_OUTPUT(*this)); - std::vector names = mOutput->enumerate(); std::cout <<"Enumerated output devices:"<< std::endl; for(size_t i = 0;i < names.size();i++) @@ -62,7 +62,6 @@ namespace MWSound catch(std::exception &e) { std::cout <<"Sound init failed: "< Date: Sat, 31 Mar 2012 14:28:19 +0200 Subject: [PATCH 51/85] ignore syntax errors and empty lines; fixed merge function --- apps/mwiniimporter/importer.cpp | 37 +++++++++++++++------------------ apps/mwiniimporter/importer.hpp | 14 ++++++------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index f662f42d64..9a76adeed8 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -25,19 +25,16 @@ strmap MwIniImporter::loadIniFile(std::string filename) { if(line[0] == '[') { if(line.length() > 2) { section = line.substr(1, line.length()-3); - continue; } - throw IniParseException(); + continue; } int pos = line.find("="); if(pos < 1) { - throw IniParseException(); + continue; } - map.insert(std::pair( - section + " " + line.substr(0,pos), line.substr(pos+1) - )); + map.insert(STRPAIR(section + ":" + line.substr(0,pos), line.substr(pos+1))); } return map; @@ -68,32 +65,32 @@ strmap MwIniImporter::loadCfgFile(std::string filename) { int pos = line.find("="); if(pos < 1) { - throw IniParseException(); + continue; } - map.insert(std::pair( - line.substr(0,pos), line.substr(pos+1) - )); + map.insert(STRPAIR(line.substr(0,pos), line.substr(pos+1))); } return map; } void MwIniImporter::merge(strmap &cfg, strmap &ini) { - strmap::iterator ini_it; - for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { - ini_it = ini.find(it->first); - - // found a key in both files - if(ini_it != ini.end()) { - cfg.erase(it); - cfg.insert(std::pair( - ini_it->first, ini_it->second - )); + strmap::iterator cfgIt; + strmap::iterator iniIt; + for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) { + if((iniIt = ini.find(it->second)) != ini.end()) { + cfg.erase(it->first); + if(!this->specialMerge(it->first, it->second, cfg, ini)) { + cfg.insert(STRPAIR(it->first, iniIt->second)); + } } } } +bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini) { + return false; +} + void MwIniImporter::writeToFile(std::string file, strmap &cfg) { boost::iostreams::stream out(file); diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 1933830f21..3ab1d892ee 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -5,17 +5,16 @@ #include #include -typedef std::map strmap; -class IniParseException : public std::exception { - virtual const char* what() const throw() { - return "unexpected end of line"; - } -}; +typedef std::map strmap; +#define STRPAIR std::make_pair class MwIniImporter { public: + MwIniImporter() { + mMergeMap.insert(STRPAIR("fps", "General:Show FPS")); + }; void setVerbose(bool verbose); strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); @@ -23,8 +22,9 @@ class MwIniImporter { void writeToFile(std::string file, strmap &cfg); private: + bool specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini); bool mVerbose; - + strmap mMergeMap; }; From b7635b3d4a12bacf31a0c4d7aad92bfaacd2b711 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 14:34:00 +0200 Subject: [PATCH 52/85] pass maps by reference --- apps/mwiniimporter/importer.cpp | 2 +- apps/mwiniimporter/importer.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 9a76adeed8..041712b216 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -87,7 +87,7 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { } } -bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini) { +bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { return false; } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 3ab1d892ee..d0034a13d4 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -22,7 +22,7 @@ class MwIniImporter { void writeToFile(std::string file, strmap &cfg); private: - bool specialMerge(std::string cfgKey, std::string iniKey, strmap cfg, strmap ini); + bool specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini); bool mVerbose; strmap mMergeMap; }; From e48d125a84165489825103ddfeabfb6e1095f74a Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 05:57:03 -0700 Subject: [PATCH 53/85] Only allow one instance of a given tracked soundid The untracked flag should probably be broken up and combined with the loop boolean into a set of flags. --- apps/openmw/mwsound/soundmanager.cpp | 42 +++++++++++++++++++++++++--- apps/openmw/mwsound/soundmanager.hpp | 3 ++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 534b5b6deb..9a71ef4210 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -67,6 +67,7 @@ namespace MWSound SoundManager::~SoundManager() { + mSingleSounds.clear(); mActiveSounds.clear(); mMusic.reset(); mOutput.reset(); @@ -231,15 +232,29 @@ namespace MWSound float volume, float pitch, bool loop, bool untracked) { + const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound; + + if(!untracked) + { + IDSoundMap::iterator inviter = mSingleSounds.find(soundId); + if(inviter != mSingleSounds.end()) + { + if(inviter->second->mPos.squaredDistance(mOutput->mPos) < + objpos.squaredDistance(mOutput->mPos)) + return sound; + inviter->second->stop(); + mSingleSounds.erase(inviter); + } + } + try { // Look up the sound in the ESM data float basevol = 1.0f; /* TODO: volume settings */ float min, max; std::string file = lookup(soundId, basevol, min, max); - const ESM::Position &pos = ptr.getCellRef().pos; - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, loop); sound->mPos = objpos; @@ -248,8 +263,13 @@ namespace MWSound sound->mMinDistance = min; sound->mMaxDistance = max; - mActiveSounds[sound] = (!untracked ? std::make_pair(ptr, soundId) : - std::make_pair(MWWorld::Ptr(), soundId)); + if(untracked) + mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); + else + { + mActiveSounds[sound] = std::make_pair(ptr, soundId); + mSingleSounds[soundId] = sound; + } } catch(std::exception &e) { @@ -265,6 +285,9 @@ namespace MWSound { if(snditer->second.first == ptr && snditer->second.second == soundId) { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -280,6 +303,9 @@ namespace MWSound { if(snditer->second.first == ptr) { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -296,6 +322,9 @@ namespace MWSound if(snditer->second.first != MWWorld::Ptr() && snditer->second.first.getCell() == cell) { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -424,7 +453,12 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(!snditer->first->isPlaying()) + { + IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); + if(inviter != mSingleSounds.end() && inviter->second == snditer->first) + mSingleSounds.erase(inviter); mActiveSounds.erase(snditer++); + } else { snditer->first->update(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 3ab1e881c3..3ab2b569ea 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -45,6 +45,9 @@ namespace MWSound typedef std::map SoundMap; SoundMap mActiveSounds; + typedef std::map IDSoundMap; + IDSoundMap mSingleSounds; + std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); From ae308b9b5fc4c73859167d4390cb61a2b264bc2f Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 07:31:55 -0700 Subject: [PATCH 54/85] Use a set of flags instead of separate booleans --- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/container.cpp | 4 ++-- apps/openmw/mwclass/door.cpp | 6 +++--- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/light.cpp | 4 ++-- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- apps/openmw/mwscript/soundextensions.cpp | 4 ++-- apps/openmw/mwsound/soundmanager.cpp | 13 ++++++------- apps/openmw/mwsound/soundmanager.hpp | 21 ++++++++++++++++++--- 17 files changed, 44 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index e95fb572f3..d27d0bc71d 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Apparatus::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index e1c2734f0e..9956a56fb5 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -60,7 +60,7 @@ namespace MWClass boost::shared_ptr Armor::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 0a81ebafb7..76370dc5c0 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -58,7 +58,7 @@ namespace MWClass { // TODO implement reading - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 4fe19ada40..2357851d75 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Clothing::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index c58a25c03e..29b3331ba9 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -85,7 +85,7 @@ namespace MWClass { // TODO check for key std::cout << "Locked container" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } else @@ -100,7 +100,7 @@ namespace MWClass { // Trap activation goes here std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 5654dff698..9d6c6a78dc 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -73,7 +73,7 @@ namespace MWClass // TODO check for key // TODO report failure to player (message, sound?). Look up behaviour of original MW. std::cout << "Locked!" << std::endl; - environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, lockedSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } @@ -81,7 +81,7 @@ namespace MWClass { // Trap activation std::cout << "Activated trap: " << ptr.getCellRef().trap << std::endl; - environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D(ptr, trapActivationSound, 1.0, 1.0); ptr.getCellRef().trap = ""; return boost::shared_ptr (new MWWorld::NullAction); } @@ -110,7 +110,7 @@ namespace MWClass // TODO return action for rotating the door // This is a little pointless, but helps with testing - environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0, false); + environment.mSoundManager->playSound3D (ptr, openSound, 1.0, 1.0); return boost::shared_ptr (new MWWorld::NullAction); } } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 1a7edf6325..cbe153ba3a 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -54,7 +54,7 @@ namespace MWClass boost::shared_ptr Ingredient::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index e2e63a89bb..71e4775916 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -59,7 +59,7 @@ namespace MWClass if (!ref->base->sound.empty()) { - environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, true); + environment.mSoundManager->playSound3D (ptr, ref->base->sound, 1.0, 1.0, MWSound::Play_Loop); } } @@ -83,7 +83,7 @@ namespace MWClass if (!(ref->base->data.flags & ESM::Light::Carry)) return boost::shared_ptr (new MWWorld::NullAction); - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 3dda2f4af0..1eef0db8ba 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -58,7 +58,7 @@ namespace MWClass boost::shared_ptr Lockpick::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 864fc1e382..def1a90a86 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Miscellaneous::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 4ab3745900..ed1733e2d2 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Potion::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 4b4d79a73e..8013e2e80f 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Probe::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 758bf40797..d49979861b 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -56,7 +56,7 @@ namespace MWClass boost::shared_ptr Repair::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 20db0cf38f..e36e9202fa 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -57,7 +57,7 @@ namespace MWClass boost::shared_ptr Weapon::activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Environment& environment) const { - environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, false, true); + environment.mSoundManager->playSound3D (ptr, getUpSoundId(ptr, environment), 1.0, 1.0, MWSound::Play_NoTrack); return boost::shared_ptr ( new MWWorld::ActionTake (ptr)); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index d5cc41b76f..7ae109075d 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop); + context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); } }; @@ -159,7 +159,7 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop); + context.getSoundManager().playSound3D (ptr, sound, volume, pitch, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); } }; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 9a71ef4210..2d743d9cbf 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -204,7 +204,7 @@ namespace MWSound } - SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop) + SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, int mode) { SoundPtr sound; try @@ -213,7 +213,7 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); - sound = mOutput->playSound(file, volume*basevol, pitch, loop); + sound = mOutput->playSound(file, volume*basevol, pitch, mode&Play_Loop); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; @@ -229,14 +229,13 @@ namespace MWSound } SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, - float volume, float pitch, bool loop, - bool untracked) + float volume, float pitch, int mode) { const ESM::Position &pos = ptr.getCellRef().pos; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound; - if(!untracked) + if((mode&Play_Single)) { IDSoundMap::iterator inviter = mSingleSounds.find(soundId); if(inviter != mSingleSounds.end()) @@ -256,14 +255,14 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); - sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, loop); + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode&Play_Loop); sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; sound->mMaxDistance = max; - if(untracked) + if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); else { diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 3ab2b569ea..03cacca237 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -30,6 +30,22 @@ namespace MWSound typedef boost::shared_ptr DecoderPtr; typedef boost::shared_ptr SoundPtr; + enum PlayMode { + Play_Normal = 0, /* tracked, non-looping, multi-instance, environment */ + Play_Loop = 1<<0, /* Sound will continually loop until explicitly stopped */ + Play_NoEnv = 1<<1, /* Do not apply environment effects (eg, underwater filters) */ + Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position + * but do not keep it updated (the sound will not move with + * the object and will not stop when the object is deleted. */ + Play_Single = 1<<3, /* (3D only) Play only a single instance of the given sound id. + * Sounds not marked as Single will not count, and all but the + * closest to the listener's position will be stopped. */ + }; + static inline int operator|(const PlayMode &a, const PlayMode &b) + { return (int)a | (int)b; } + static inline int operator&(const PlayMode &a, const PlayMode &b) + { return (int)a & (int)b; } + class SoundManager { Ogre::ResourceGroupManager& mResourceMgr; @@ -90,12 +106,11 @@ namespace MWSound bool sayDone(MWWorld::Ptr reference) const; ///< Is actor not speaking? - SoundPtr playSound(const std::string& soundId, float volume, float pitch, bool loop=false); + SoundPtr playSound(const std::string& soundId, float volume, float pitch, int mode=Play_Normal); ///< Play a sound, independently of 3D-position SoundPtr playSound3D(MWWorld::Ptr reference, const std::string& soundId, - float volume, float pitch, bool loop, - bool untracked=false); + float volume, float pitch, int mode=Play_Normal); ///< Play a sound from an object void stopSound3D(MWWorld::Ptr reference, const std::string& soundId); From 977e7ac9a3c49a4771c2f8ede2193429399a5ee0 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 07:41:26 -0700 Subject: [PATCH 55/85] Remove the Play_Single flag. It's not correct. --- apps/openmw/mwscript/soundextensions.cpp | 4 +-- apps/openmw/mwsound/soundmanager.cpp | 36 ++---------------------- apps/openmw/mwsound/soundmanager.hpp | 6 ---- 3 files changed, 4 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 7ae109075d..b4386a8a0d 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript std::string sound = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); + context.getSoundManager().playSound3D (ptr, sound, 1.0, 1.0, mLoop ? MWSound::Play_Loop : 0); } }; @@ -159,7 +159,7 @@ namespace MWScript Interpreter::Type_Float pitch = runtime[0].mFloat; runtime.pop(); - context.getSoundManager().playSound3D (ptr, sound, volume, pitch, MWSound::Play_Single | (mLoop ? MWSound::Play_Loop : 0)); + context.getSoundManager().playSound3D (ptr, sound, volume, pitch, mLoop ? MWSound::Play_Loop : 0); } }; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 2d743d9cbf..6249c4e4df 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -67,7 +67,6 @@ namespace MWSound SoundManager::~SoundManager() { - mSingleSounds.clear(); mActiveSounds.clear(); mMusic.reset(); mOutput.reset(); @@ -231,29 +230,15 @@ namespace MWSound SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId, float volume, float pitch, int mode) { - const ESM::Position &pos = ptr.getCellRef().pos; - const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound; - - if((mode&Play_Single)) - { - IDSoundMap::iterator inviter = mSingleSounds.find(soundId); - if(inviter != mSingleSounds.end()) - { - if(inviter->second->mPos.squaredDistance(mOutput->mPos) < - objpos.squaredDistance(mOutput->mPos)) - return sound; - inviter->second->stop(); - mSingleSounds.erase(inviter); - } - } - try { // Look up the sound in the ESM data float basevol = 1.0f; /* TODO: volume settings */ float min, max; std::string file = lookup(soundId, basevol, min, max); + const ESM::Position &pos = ptr.getCellRef().pos; + const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode&Play_Loop); sound->mPos = objpos; @@ -265,10 +250,7 @@ namespace MWSound if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); else - { mActiveSounds[sound] = std::make_pair(ptr, soundId); - mSingleSounds[soundId] = sound; - } } catch(std::exception &e) { @@ -284,9 +266,6 @@ namespace MWSound { if(snditer->second.first == ptr && snditer->second.second == soundId) { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -302,9 +281,6 @@ namespace MWSound { if(snditer->second.first == ptr) { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -321,9 +297,6 @@ namespace MWSound if(snditer->second.first != MWWorld::Ptr() && snditer->second.first.getCell() == cell) { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); snditer->first->stop(); mActiveSounds.erase(snditer++); } @@ -452,12 +425,7 @@ namespace MWSound while(snditer != mActiveSounds.end()) { if(!snditer->first->isPlaying()) - { - IDSoundMap::iterator inviter = mSingleSounds.find(snditer->second.second); - if(inviter != mSingleSounds.end() && inviter->second == snditer->first) - mSingleSounds.erase(inviter); mActiveSounds.erase(snditer++); - } else { snditer->first->update(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index 03cacca237..de5cca839c 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -37,9 +37,6 @@ namespace MWSound Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ - Play_Single = 1<<3, /* (3D only) Play only a single instance of the given sound id. - * Sounds not marked as Single will not count, and all but the - * closest to the listener's position will be stopped. */ }; static inline int operator|(const PlayMode &a, const PlayMode &b) { return (int)a | (int)b; } @@ -61,9 +58,6 @@ namespace MWSound typedef std::map SoundMap; SoundMap mActiveSounds; - typedef std::map IDSoundMap; - IDSoundMap mSingleSounds; - std::string lookup(const std::string &soundId, float &volume, float &min, float &max); void streamMusicFull(const std::string& filename); From ceedae4a1af3c155f31221376ac9ef66c3e9dca8 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 16:54:53 +0200 Subject: [PATCH 56/85] technical corrections --- apps/mwiniimporter/importer.cpp | 18 +++++++++++++++--- apps/mwiniimporter/importer.hpp | 5 +---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 041712b216..09088774b4 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -3,6 +3,18 @@ #include #include +MwIniImporter::MwIniImporter() { + const char *map[][2] = + { + { "fps", "General:Show FPS" }, + { 0, 0 } + }; + + for(int i=0; map[i][0]; i++) { + mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); + } +} + void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } @@ -34,7 +46,7 @@ strmap MwIniImporter::loadIniFile(std::string filename) { continue; } - map.insert(STRPAIR(section + ":" + line.substr(0,pos), line.substr(pos+1))); + map.insert(std::make_pair(section + ":" + line.substr(0,pos), line.substr(pos+1))); } return map; @@ -68,7 +80,7 @@ strmap MwIniImporter::loadCfgFile(std::string filename) { continue; } - map.insert(STRPAIR(line.substr(0,pos), line.substr(pos+1))); + map.insert(std::make_pair(line.substr(0,pos), line.substr(pos+1))); } return map; @@ -81,7 +93,7 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { if((iniIt = ini.find(it->second)) != ini.end()) { cfg.erase(it->first); if(!this->specialMerge(it->first, it->second, cfg, ini)) { - cfg.insert(STRPAIR(it->first, iniIt->second)); + cfg.insert(std::make_pair(it->first, iniIt->second)); } } } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index d0034a13d4..ad5aaacde3 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -7,14 +7,11 @@ typedef std::map strmap; -#define STRPAIR std::make_pair class MwIniImporter { public: - MwIniImporter() { - mMergeMap.insert(STRPAIR("fps", "General:Show FPS")); - }; + MwIniImporter(); void setVerbose(bool verbose); strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); From 575474ff69459e52e0d7bba14fadc86da4fd8f84 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 08:14:39 -0700 Subject: [PATCH 57/85] Pass the mode flags to the sound output play methods --- apps/openmw/mwsound/openal_output.cpp | 8 ++++---- apps/openmw/mwsound/openal_output.hpp | 4 ++-- apps/openmw/mwsound/sound.hpp | 2 ++ apps/openmw/mwsound/sound_output.hpp | 4 ++-- apps/openmw/mwsound/soundmanager.cpp | 8 +++++--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 6a4e3fe570..ddf4df7057 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -606,7 +606,7 @@ void OpenAL_Output::bufferFinished(ALuint buf) } -SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, bool loop) +SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -642,7 +642,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE); - alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE); throwALerror(); alSourcei(src, AL_BUFFER, buf); @@ -653,7 +653,7 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float } SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, - float min, float max, bool loop) + float min, float max, int flags) { boost::shared_ptr sound; ALuint src=0, buf=0; @@ -690,7 +690,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector alSourcef(src, AL_PITCH, pitch); alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE); - alSourcei(src, AL_LOOPING, (loop?AL_TRUE:AL_FALSE)); + alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE); throwALerror(); alSourcei(src, AL_BUFFER, buf); diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 35966cc29b..a709576bae 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -40,9 +40,9 @@ namespace MWSound virtual void init(const std::string &devname=""); virtual void deinit(); - virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop); + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, bool loop); + float volume, float pitch, float min, float max, int flags); virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir); diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index e23830cdb9..ca12ec5571 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -18,6 +18,7 @@ namespace MWSound float mBaseVolume; float mMinDistance; float mMaxDistance; + int mFlags; public: virtual void stop() = 0; @@ -30,6 +31,7 @@ namespace MWSound , mBaseVolume(1.0f) , mMinDistance(20.0f) /* 1 * min_range_scale */ , mMaxDistance(12750.0f) /* 255 * max_range_scale */ + , mFlags(Play_Normal) { } virtual ~Sound() { } diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index ed3dda2f34..1507e18472 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -24,9 +24,9 @@ namespace MWSound virtual void init(const std::string &devname="") = 0; virtual void deinit() = 0; - virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, bool loop) = 0; + virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, - float volume, float pitch, float min, float max, bool loop) = 0; + float volume, float pitch, float min, float max, int flags) = 0; virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) = 0; diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 6249c4e4df..2c2e6e9f91 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -185,7 +185,7 @@ namespace MWSound const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); SoundPtr sound = mOutput->playSound3D(filePath, objpos, basevol, 1.0f, - 20.0f, 12750.0f, false); + 20.0f, 12750.0f, Play_Normal); sound->mPos = objpos; sound->mBaseVolume = basevol; @@ -212,11 +212,12 @@ namespace MWSound float min, max; std::string file = lookup(soundId, basevol, min, max); - sound = mOutput->playSound(file, volume*basevol, pitch, mode&Play_Loop); + sound = mOutput->playSound(file, volume*basevol, pitch, mode); sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; sound->mMaxDistance = max; + sound->mFlags = mode; mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); } @@ -240,12 +241,13 @@ namespace MWSound const ESM::Position &pos = ptr.getCellRef().pos; const Ogre::Vector3 objpos(pos.pos[0], pos.pos[1], pos.pos[2]); - sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode&Play_Loop); + sound = mOutput->playSound3D(file, objpos, volume*basevol, pitch, min, max, mode); sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; sound->mMinDistance = min; sound->mMaxDistance = max; + sound->mFlags = mode; if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); From 2a7885b514ab183edc3bfd8f1654cbfcaa501c3f Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sat, 31 Mar 2012 19:36:07 +0400 Subject: [PATCH 58/85] Synchronized OS X icon with our current app icon --- files/mac/openmw.icns | Bin 153405 -> 134980 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/files/mac/openmw.icns b/files/mac/openmw.icns index dfea24660669401eb1a2ae9fb81c142891c7be03..3ff899a799e17fa69258323be59c470633cfec68 100644 GIT binary patch literal 134980 zcmc~y&MRhM;&;g`Ha22lVCLZX`|A0tM~^>0zSH=E`}wQCKNd`zwDC+)R!`%M`l$=Q ze_uFh%k99JS#ze$m@#wO!tX!kPursByK3Up?bByWo3ilxj~Vj@K}pJFwn>!-ANs~D%~ykAGi2fBf-Q{5(emqA5xU^}yS8BN)T)+JcU92{kHlr)9_5${*H*ZS zNK48{cukr6{gJQ zkDmVh)?HUKZDn|1abrV6V{P}hZ#~shH(7Z!x7IZ@Hr4ff`_@%4QO0^wRpFebhKAan zZ$Ih^O+{MDJN!bcn|q5pzJE&#=JcPwq{Cm>ysX=)@9UdjZBD<$!eDhNehYoS$zSf+ zNb_Z-n{x{Z3iDglcYnL1Bg9wMH8qr%mtPnzTqKysW{1dvr{i@*RQAsWAuCdnX>T2!z_GPj+laO~?ZA+?QVM}e#w{M-z-s}=7 zJxy(O6^%7r-@Z+3?k}-3&8(~I>}YS9`0d}v5AVMIdUfm3+r|%!AO96wB7#JBqc*_6(=?pv!Obkqn3=GT+6aW9OWZ>SL&%naS z!@$7c^8f#NW@d(#|6vS9y$nnY>;C^w6DtU6{J+!a-F60M!KeR!PWbTu-~a!=e*7b|NLLdz`&^f?f*|@hLZn({{Q`7DIv(n`{4h7JCR>M-~a#r@Bgo4 z28M*EMtF)|tuP*)8o8Mf+#``2YXMf*G@B&7QL8 z%KzIrYJPq5=d{lfo3Q{SF@M^OnUhyM`G3Sfuwe1r=`&}x%@Ch9A0jhz+Vbxy?}DxoJsu?r%ay{ zX%`z^y=dyHmyzjU`Jc@V(~6}<+~)PIx_owFXG^G?ziCK&&Efi4#?`Yy27GC%?)4Ir z$ecO75C3;M0owYC{S6*kn%|b8Ek>Y&TO=IhBwqFX!wz z(?Igqa?5koq!miqixT|}WFCe8u}!1bhzSY=7|-Uag)%&l=$mJn3Z zlT#2-^b+WqJp-)ZT6C1BqO?})?1pLeb1D)8btQG=g~a7@=TC#kUkMIzRFIUfojZNn z+~X%}4TQxMRZPvz?MtT4m_Ga4|NpNdy{+YCtSxzx>?(D{#JY^GCLpv9B6 zLk8^qA9LrF%FD?Zy2MSGHGRg6>9hL1^y4PYn%*!&5F$T+>ilF05qY1)(n&KLX0T45 zJuT6%0j_)gv>Edgg!t8>OFJh{ojG&H^qC8$w1ZNZG*tJr8PgXOtFTK)wJ%saX%fhW znX_j$&X9sh&zv!B{-iJ&PW99k$JWo8J!8i7=}j}(VX_S~c&5!<*q>pisN_>xQ!~AF z1{+xF--qV+oNqt<`0?@4m5cXZwZCWl@NeFKP;R_+z3~Ru&6`&*UA+F_%Kn4b+ir+n zzxC(;|L;?}`+9r(yW6L(-8d!EKB=LtSFEpl%J={Of6SXYefo?^^Dq3qnk?zsHFs9~ zbTM$i{+Kg)>a+<r@Gd)a)sfCT}@W!_JvIf70}xmR^}1*Pn|M1tL53+ zNz)pp8c*-(n>rh0@Z8ScX)|DwrL>wpR<}L%}VED zjhHuOdc(B($#xoTEfc0r>)t&tVa}`{|NsB$YnnLOj7_p@W`9rL#3{4f)qFjQ7ED_D z#65a0SiY`ma;^Y}^_=eI=TFXQZE)4KmUC+?-9JIPcosAJbZ`J%3XThq;FIc_H+SCbG6P{APEmOwadsig_L)<_ z@|V4Q%tiU78fP?2uAiCfXDiFEAi~Kjm_B#%RIvO-Cl?)Ie!+^_Qzp+maI{R7i$_FK zMO9fRYtpo-Grs-*|I*D$Ls*b6eeTrW3A+n;Soy_Ncm<7pLdzDcm{-De6}=bj_GDb?T%{Z#&!RO;csS-up3YR-S;cpn{HX z-}EU{r%svCZ6W8^KYdEWR6&URoJn(|IC(_P{qp;#HB4ojGIMf-Q`NLanD#l7r_7CD zXOr>EZ|j>hZQ9f+)8eR{8W=?CIDg~3C zHg)oxK7SDgxwwUg*3O(Ub?THUO;g!nvJF#tCQqN&on$T{<(gMgHl=kc8(8Y!ho<*j z@7{j=@#Eve%NOpxK+2Do&o^G+ym;x{nN#PlpV+*!{etNE%YXj=|JGPmSy540R$Mn> zQiZFIcUD`4SY=t`xBvgYcQ>{)x72rUzPdR`$g;Alt*u$SrLp__|Nq}R>l>SDyLVq( zAkQ4t)7sG3)Y>T0RNo1aYO3#CzPFl#BeScatf7vfskKoYD%DV5Ield+3rkE_U2{d+ z{O+3OM(HM)zJ{Eti5aZSc3t(2^<6<}M`zSGH8e^$S5!5C%>C9~m{3`!z$8)A+|bz2 zS|E};uePDFzNx;Wps53FZ&YrHI|q|@XG3#SV`H6`WN=VTV`Jr#M*og>P--dl%}i8b z5h`n~sj8`KYBv$rQHkwo=s#%W4Uvy=j&tN;(eJG6-?FYbHN{XyL&!WYcwQ+_LMzDp zFTr+xs$8tWRi!(w?3xm7z=Q7#}>!A>j`pl2}_9Z^UJG=2pCj0gXK?Y7;5r!@|ShCceW=h z@Nu&9iSY8X@v0ZKHh@F#l#+%#9~XaKOMP8arLC^2Fqa4~I~PxVcy~i%eb@K@|4+-S zNbz!Uq_sCRG|rqBAi>VbCm=2%F5_L%+}Qf{|Nm#&vJ$*JY@r>EmGM0>oGje@;#}PF zdiwT7-4z+{|NsACCnnCv%W2!*nr!66CZR1XqbR`3AtEU$Dj5Lsz~5*Q5q=(S#hOlE z1qp-lTv<>_J*d$`bMx1zx9+#a`WMIx7M|DT3Z@HeT_zOBYV1pPL4*dV~|IhzI1`bds zhX2X`|1U)tSQ+yECor&pdJGQ#|Nj5?|D6;ABLg!dBg3ixKmW%uFf{-FUkH+CVVM5^ z_y0ft*E5Kg?!9wrb@t`|zy7aP+5i9N|4@)T1Jm{YfB*mfpKALK6hQyM;QROgfBt_{ z0=bhx?k8A$DM zU}9ph{RUC||KIC#Z80!E3{{Q#?LqUex|NoCmSpEO|_y5LuH=duLpbqjN z6NAQ&|DgU}97EIp1#ArLSO5R~AJ4!J>OF!JEHlGgumR^8sd?F?jj24;q;|Nnmf{(naUlzN$%7&u(GVVc1-3xm(||Nnme ze;>iX$jHdZ0!z7INf5!zAk_Zu|G$6#_gI7EK)q^^Fak3(F$l%4y!P(@@7=DDctOa4 zSzs+p!qPIb0$@HS!N|gbDaeqS0qVkvK5qCw`7h(2KmC6<{b%}p@<`iJzGKIa?BBO* z&ygdCHqKjq=venr&Lby(gF4nfX7o2t6qqn^a&OzjMT=Ifo0aPvl;1p|XCf!4U;FR> z{~rryG|%FnGh@QCa}V#Hogb!ZoIi8HoZeYXGZy}6{Ll1b{?1q`WPz6#y=1&8Sx~)8N;o8+WP6^G<`BP`iY??HA(#)CNP&LvZHM1tp+HvUA zVNY%;rMh`jXUv$@)vGR8bo?cusac1ibwwcqQD*1oRpEk37`l7{aV|nC7+~-bjn!z!1viIayPuEPD**t@N z=JdL0pq_Z+fBqlyX7u#Vm^HswNm5*;YtHoM8SK*&%x0fHG;dNAwG-6z`d`fzZT%BtL}es(`semd-S^<#=Y*q~lSMXW<+aGX-kFpZ|xlYD!B2#pD#E zQ`>5b5&~SUjkV-uwVYjbjg=*JoMXM@jl6=|W={vDk^ley{6CypmX+r#Ca<8}vS`ly z`O|x{?6nkR#Drwj3}n?D?c{|uExhXIOb4eFu=>bcj`&0;F&R1aw%HBS#AeJ|&>dl- zp(H6FV4^23!6zWAZeBWPdczE1a3A;2|09tp;X&pSvWmJ*bEh}X5T4YVALZ%hpf96g zD$Xw^E~!+wXlCP#FsSPNVe$T+rc%=KYNhk0&zLc5;?k`PCpBl7OYuu880pE%s;F6& zOqn`k)~p$z;Q8}^zkg(av$2GvjBFt&=S-h?;MC>qv5JC1!YXP)2KLqsHub(meG~fn zC(Ql+|KH#LcSD1H>13>~ zDk-mQR5f?{%mj~cYuo7FnIRrg<*r)#3I4904%S9iF&&Gh&->B%U;F2*FdJhHH8}-k zy@=T}rg?jLcofZ;e$T_fcUTXck>5VfuXSFs|mj&p>ESS~` zRVesl-i)cv>Pj+lDmwOFX)`N&XMxI$s+f>O=ir%bP*wat=1-m5prxoNC1+^i8BsH* zbq2@G>50aMF$oi9!i*C5F@M_h`PuS{Qle`1o{>3Svsz|wOrJR;OH(JgV-`%6AgFkp zJ~vrTOhQCAA~wCEb0%2X+^!h+vI#R=pf-Za@rD_^^AbgbC527WlFK`LXUuAu!8(1; z{3#7hFvB3K_@~dymgN@{(n~FCo-kq3jG4_dSf)>#IU8;#NLk|yf$0mH&3J``)DmhY z&RsTT;>=ksGZ?1Bya-a$G(&LO+}Y`x{Jes?IWzX3-87|t`ph0A7d6iioIYdW)Knu$ zZgx4J?vq!y%$`0AR`4_Zm^-Cqnh>ZzJiQ^_MNN=f+&DffrFq8G&S^|j=Kg5>&-m|k z$1C0!uik(9`1$H6t__o>}|&At46{awv{bLTEuH95(|HKU=YyO*o4d+K*kOX$bE zsm;^*XHM%|eCqb~lhb{~)iS5go7Fp=6Fl_v@BfcElbfdUPw$_0{NuN8+XHym1E>3z|^VJCNJ4{^1}HL20q!=xs#?&ukD{OVcN9rsY*zSruR+TwD-tf2S!2B z$~lv!PMy)#uwlu>3DdhEgJBT2OrOv`cg>CsR*b^jiSs5mOy!s|H^275nT37RTBk}) zn>-afdIM29t$Fg?`747N#5iqdPidOUF>Qib>(d7-Cr)dgDm`sV^+(shCh%a^zkmOK&Z_E~FkxC4rx2Ut+{sg?Pn$e>>h!6z>J79^cNq3#WIkyS77J7jFLVy28fp zz7S4fKKY(m-II3Tep!ER?S$s?Y%?88OBHF~w#de1hvVgKdcmV-|Ni~|Qdd~n)}q5B z%xyipwSCve|No!woZX%1Zlq>rC?%t&Y}2r!N>$%)1}wZ^7T4C)=1K62aK-euPn$li zyQ6dB%-$SF4MjsuQ3(+x--)eS1{P(r!Glx({{Fv|RbNq29>OCeEaVnyA;rbX!^AUkifQF6(8$yrumyYElY)aycmxHdnr1Xi7MnU_UWccGq$nQ;hpGaP02_y(gi7A5 zDGgJF!SVa&|9+29PkRkMAz|6tSyLLP3Qg!ray8V`mJ^Uw=VjyJGdySKZiZ#KBfyyNU(qb0{DAjlU2 zN+YO^(f*_7haa{z=m{P>~fD6cN|xo-ui5o^!r!y_BL# zpplY_leUDkw3vjHtc(aZvsl8!dEdbr8cg*RCB*qf#N>)*Po5rZ=%%dZ*Ez#c-#yDv zQX$yE&{#u7UMIL^{*>82KTc&l?2DvzyCjG z&rFh*6cH8^Q_!|;pA%C#dvfDc&go6nMOl`T!Sg0VN8!Px$CSw?qN0Mr;xd|s(KCv> zrZ-PzpHk%E5TNBUy=|%(%)mL5X4gmyiwFoQs2I7F&uW>gWPT9=9!V_&m-zPSEmJwBOq-S{DIeB61KHBaQ>V<1D6=K}9sZ-~Mb8rc8X-0$_YTG*xi&tQm>& ztgIZWsgrk}+%U0c%CsJ270puxr%atUDPEhOi9y7v<;dmDGp0=MMyQxMp>>krgeh~T z)$LPnp;`iDSY{P;dSJzgHbEd7r<0|LNnaN7pW&J$duVi=LN^uTW~y z-oFfg8vZi>`STmn>-@EMd)p4)ox8Sg-n4ez)-9`Nw9VMovx95<-d~_*A!wwyrGmGz zB)_7ivAw&(M?pU@yQhL1JX#D&a@~zBP0jo*O*MUMc5Yo+XUi`e)!N(?N$F~=?{Abv zR?yhkRMW9$$Nt?>OssPGo%IbZ4NX0bN+?R2>N*#!ShvoFfm5`oqpq94PC>THxiQr1+L(=c)NRDA|M*1)d%hDMHt zu8^4h8~dx9S{tRB8X&`uV7J$`q~*1=bviN#uo$#AG&XWJG*`)G95^_&wh1)S-H=v~ zFt)KQGPk-WnV*Y8pt!Z6rIF2&zhwD>riP|Q>884lveFjNnCQQM|G#zB#v~V&@E)7qo$fkU@%jvqFmU^Md#|+4+mXL!(WNjSX!H zvLc3oj#1!Y(~4b7B;e}5cLjuIWF*?K@i3Wo)HgOa)zvjNH@0TU$|xA=8r3v4G}o>= z7r~bcGyhkmZA4<6uQ&%cdqR6-V`WWyeM@b5O?!^CoT8?pWOPqU@uY1tBxLhp8h$5R zc}IpAbMSDARkT%AFWz@@!=!?U&_G35RaFTAyHfjrt|cBqnpNO2aL@=)w3V~3moz6Y zt6pbe%8av59v_%n6KtrVAfc=vBrGi{@7WPACTraS9x?v+@Ba(0dKXt0YXL4k7T?my z?AnHg%KVCwKr1zAF*zxI0X}ifhzuzO`Gj`xAo{<*|4&;uJK5`Ta`SQXe{p84hoPdls1Og2u)M6W z7(cg=tiBdcm8iUGZfgT1X8-)(s9|GdqRh#|&7IfX+R@om8lWh|$IHpiEg-@zAg933 zE+iqB+zv}MfBvtRH`UUT<>cn#%WJ7`sBdiUDm4%l;^$&xlMvvHx3SR$rhJ2(wxXOEH#ZMgd<>b!kpYZY%ZWlp_-KH8I#mlcpA9s zMMZg7`IPi^_02r;rY>Dr9`X!SkA1e3krNZ<=HcTuXlZC@OS22l7v~Y;;1%STmk{9N z;+4?UFtG8BaCeDphSZaO60)MgJlwqe(iP41?ZJv3+JYjIPKsg@Ie5efb1{R$%JL_8-B_Xl<@BjDChI$oVK5kxqVQE>LmZXa2=0^6WKvi{pDWm3A=+p$r z%+A{O3;|wVZXQty1(nE-)<%w|CR+hxSp)-h`my1I{T3*d0r=_Klv!SWU zUqDDVy{(}c+1~obhIUVOE-nrcbq!sYqNbKcj)une0vA=UN^n^R4c+hH62HTPnVp+m z(m>lfrmU%{rID?nwX-%Qy{Q?!TyN+I<7VPwlQ6OJ$*intY-(;~X{c{%YiU8RAp{ya zbCelb*+tAeVoJN~s~VeI8W|c|QI$0|3f8wZ2T3q8u}gdBPhU5`uA;H28%a%9LvvG; zU_(<^U7!*dBZGix&W58)nj4zCnz$kP;otvn?KSl+b%HhZZ4Fso2I8y?>nIW(NC<|Ns5{{rm5~W>yA5{$OHY>HGix4`|Zoof`u) zD|ng#**sJ>GlST{|G)qI2TfvVGO&VHzTpcfCI*@7|9^qp{{N{e0}B&_V-RE+9jd7? z9utG;)&HPLpMU?qSu(INFfm9!y`IVgo|=NHL5V#^rd?puK@%RJ=?d_a*S}|O(AgeP z%z$PXpjsK28Cqb<>@8ADo2#i^$=|9{m3>1SpTz4u7~7K6+Tz7XaA z{(q2VU}RuoW?^PxWrzjOAWdUnW?*3uJM;gWJV+B^Ky}4-1B1&m&}5w& z)O;3(3UJW>|M&li00T$F@gM*H|Ns4JYd6S8zyHsc*!2JJ-+%v~@Spy>Mt z3x1G}zaY-by*p|_8Ha)O7Q})-|MxO5F*CS=l{FupczxJj{kpt|M~at|A!bz0%B$e13BP7C>BE**cdkb|Mlnp zJaMKwAbbA*KKbY$DC0fuR0KH|>>z=s5D)zMzm9>0;qd=o|MM7F85aEi^Z(!fZ&FM; z5tUKqJn$KF24;p{n1g=lFfi=?|IwF$l@*#_eky?DoB>oOfr1}Ag{}m3>!1I#85q|5 zw+9y~3@5-AI5DuYAYu!wg_)rhX2BmThJ{rOY~Ts_wEw^U|Nh^IND5%ZU?1>Y1$*xQ z-~UG#+(aSaz{oHgc4;fS715_G@;)N(eUs8cPF?8W(KXEH?LqJ1UDVTU}mWM4^Ae3 z|9|y?1s)?aBLiD}AT&fl$`F`^A?`b<1Oycxc?_`GU?yy{vk*-z3|c4tgNlWJfBvtN zWMG08bg&t2h$3W?nSnXw(f@z{e*gOU|6MT`mT*K?4T@+c2JWQezd?!e-~Y#r+MrB@ zrUaD>@*jg{?y}2ozy136|K$X6f_6Y$%OoJLsV2$KiKtRg%|+oc!HWVEVZ0nhCMG7( zQgbYdGD|>H-PvnEQXrp#@VdWUf7bu~|G(=$)1Pyv7M+$lb^83di{}sRKX7u_@^zax zt>3(H&Fa-FR~$cf?99s3OsCHM>Hg33b6VfL3G)5@6DLoZ)Y6z;kX+U>V`gtpPgiGe zb5nhMu!FZt)s+4f6PWs@{p|kF^lSc%IkV(v&YClSYENT*%ghzq_8r{5dcl$n8E0(`U|~)ze%)`}pxwhj(sWv1swyUEAhmyP2u! zJEb*DpF4Zj>KRPa=7Eg=Id9qwkovh(%4(;de)IOm;ccr|uUxZl@6tjWC0PwM{qSk? z=gge87D@dykotL(>RNZc`u5@ap?yd8ZrpTaf0MhSh^&^fUQqMgnbW4PM^Zm^hTQb| zQ)(uj`}*(Qo!fV=o;a}U(CKzVVJT&GHI;zoxpSv)LQ*|>hWzw7vn!iU{Q3Rz$Hy1% z?p!@~?PQOspp=5LhN4Bzta;O>Z$?r+X@=a)x$W(HKmPmp@yqvb-#^~F{a~4^pooH! zyrOzY+rsJ7w<4*YFhg$o>=~8QU;KRY`upeKe}BDu{&AC!fS`*q&bKj-mC$VyAgDk+7|m_2P5lHwkaL#8*)y8htu%Xfc1|96>eA>@reP zGKxy7(Q{@_-;JcY8>D(t*_N{huHL%)?ANPz_bS7h)c=JE9llQnASdndET5^vo<1G(=kJK`m6~(dv@$Uy#K<5J9kbW zx$`QOTS`VkTvA3+Eobr6wi%o=rxrI%oG^RVjOoY;s2yzE^qs4AA3t*R(zPq6_g=aY z#x5f+CLt*$ry4PRddCdTnF$foYg#AHoIh&~lAUcJuT5&(zG2JWQ-{x=K6!c1{(~O; z(h}n0Qj&5ip|fXB@0h_eb52IpnN7WIMg6m8PG5v!xvxcAV3WBZS7 zwGogN6PJ>dQcw$;Gh=$^4BqK8XC+i0I=^S*oXHdB&R&6{U3S)#(#7)^t=n|?*rEM< zx8B{NCn6;!AucX0r(xbQcUt!h-s!XZlCy4IIQjP8yt8^o!&P?VEXLZVwaNdw~t?X|7}O>?3vT2 z&tRTDXU=rcgeuIOrWrER=hautnKozsvej!=Em^&7yQ-L+j1(vkWmSD%ci;0CeC1=R-EGQJmt&nh6gPF?^I8j1u|$}vCkCnL@>;t#u>8H=a;lhojQB& zq6PDpES^7ak-4@^Ath2j& z%erSHyQyJ@?DTn!_0uL!n>Bm(oVoMoPF$KNDJvr_CLty(tK>6d`XrE1g>f;~HlFLR z9zJ*a@UtuZvnJ1An?AQ`U+sihXlBivTsm#SglRKp&73`N-pr}f3?!taB*ereBoz%B z=S`nHLugiiWP-JWwdcOGdv@&GdSpY}-02f$a7ABR z^f`UgKJD|!nLiEHv{}6+GbT-)JZ<*e*)wKLnLR^IPDVyTR9I9}R!5Jvo^8lTsn8&>^ZZil}(%3GlOf^w52edes`6Q*=3%1B8_h>Hk|%79!5GH^~qdYGq!t(8&4+-Xy% zOqnpDcG9fw8QimGH(&nsYPYL>F2aclKj%%GKBv5?ee%?qvp_pjruEMb7nhe57ZVi` zmy-6KH+}jHk(twygWa5L?M&^Ox_f(Do0_Y;XHV|{<;&@EIeFHcsZ&AQD<^em%Sua#i-?IzNa-}roj!er@boz)zFyvT_U7gp%@s9OHI-#W zvpQz*O`kI*^UAL~?+&_I+It|3l>Iq(>fGX*zP9O8XUv#3ed?5nv*N{+Wh5koMI@x8 ztY^&-oj$iR%-O}s)!rtkKC8GqH!CN;8I(c!r_Y(3d;Qm=XWyn;IokUpjFkU5cgEzT z_Rg-!6F}>LCQa;`lO!T1Eg>cQGmTD7d~-Tjbk_&INCW@T+_*QCygQzuWJ)Y=;(Atx;*D@%VE+Jj*LLuNrs*^1_V!(SeE7x66%i_V93baYgxV`oszSos+`FWF#ddMWv)ARjen>Y@FdXefq4q zlWX&Px1N6c?%m^C=k`r*t1l?2Ev%Zhq|(RL$=}Dx!zJIt#@Wf%!P4Ay!p!M&W;d6# zELpaB$G&5C)>&CN*}Hh;Arimr@9DvHHk_QS_-Qgs$07HdpkQDtVLyI zqNWo<)wOK*2q zLsPY?w2X|Ll%$-Dyqe8~S<~lCO>JAhX!VYhH(!4H@$c{7FHf$V*uG}Ttm%E-CArCA zzJd1U_I3`=_DNP+dWO1sdb&Es#zwY2iG__*rY~H%GSz@Yx*J%d z2R5u-Hf!3%j*{HWn1BceJxeP)d%JWiZ3A6hEp0VfMQw8@|Fp7>$#WMjud=Xq^zioy z@@tt3t))BvXZ-vBIkm64rlzg7zM-|YxXo8oRz^x*K~73aQ8{P9yz2C6UEMPmZ@%*V z|DT^lkUEtfHi#rYx_j zWn%3bmRjA?zjUI5rK7WtuTMl+F{r?VSeX9j|J?kBqNaw9%GSEN*0Owc2}v1gDLDmM z1x2&zc~Ns`POB~o(~}Vt5K~lEQ&Ck>QIu9N@hzJ=v9-FSI3vQ_+s{tZ!oryKhvN8s~f7~OB*_BODdYG$`a(H zWTa)}6<7_s$*bg8xWJ!)b6LEV`k;y;~Nwa5!yBfyi#u7&yN4%|NehS zt1T#~C@Cv0D#*{y@{o|0kdl#CXpr+!w#&@SDJ;s*c5|@MF)+5XwX(FdF*7mJ)z;Bc zRaaM(Q?!WA4tKIPH`X)IHFi;yl$MrL)igA7boa~XEYX)y(KfPj@eA>diLReJy&dXB zxqtsZ7M2#2SJze4Ruq?~=XpqgiZ*#rn95tuUf4L_dES(nP5DudP7Y3vPL4Jfdiwer z8p^6l$|_0{k_O=kj`n71I(kM{l9EzV%3Ass4mMsn^}#Z7%Bp%McFw**5x&I>r@=y0 z?%)4Uxj7}Z74-$h`9+1P1r8#h9-Wf1vb2Jt?d0iA(;Q~boU>ro#Nyn*02g~_YhzPA zb!BBmd07Po8A(wIOAiM_4Gldr3o&s?6&(XhbBCC+a5+(VIb{uFb7yCd!0^PWT?nsz z$;hp&t|-ng$jZ;o%drrZl9G{@SC*GoQ%jvct!0M&^l8&)&6_u6Vp)utv8k!1hKiD+ zyo`*rgp{N>pNNj3rn0W3g|LW>x~`r@KyH+(fP}P+qME*`nZ0vhaO$jC(>oDXea*?p z&aKQy&B;wlPtLItlb4cFkdu>FR8`AdFs);T)wJm|XD*ml6m4K?proiMCo3l{Eh#B3 zDk{jYpr@j4X(l43tfudq5TL;=AR;CqtEgpQVPWMLn=)(WbR-XbP0CKqF3n6%Ns5k+ z&oq#hmXeZ_m6BJ`(8yRYt!D<$^cf4L7sb1}=qby|N;OETN=S+d3JUPaXsVc4i^!>I z*w{$(@CyqGiAYGxYv>pn*`x+E&7BTfWeG}sa{vB+OO8uQEzC?#iHb{1j8v78mIftU zd1Vcij0MyBW*AMIK7VdpMS!W1wxYbOw3w(MKOa9Ix0JT2m7I}=j36hUh^UC5h^VBD zl7_B7to>0?&j$jPdxt7p%j)<45w+T6O# z6mMU9Yjbl$V;xNeX$esgejWk$WK{`H4qkqKK_L-QF$rmDB~5J;Pqzk8&ka-(b^Mq6 z_y2owa%N^yYI0n-Z%AO2yo|K8l)SvGlA69@CdlOm)92Te6orQedAhsWTU%Nf>guVf zswk<+JJ)H5iVFz~2?&Y^3kyj|NXsdz8HBdXn+Dp~0Co4bmXq%rKg^uzbR) z=AQQIip-eE(2!ssUvCd9Woco4PB}9nXGaNHNl>3iLQ+~rRz}?-tYh{ZWS4(QPR~e= zO$_l3aB+9{Q1*q@`99@gp62wO)&uh zK|wxgLs20=FC{ry8EGkLSs7U+WkqEr)5w+uv!=sSjL^UTpQGYqV`3u$0(|W}on6#r zWE-S7WMt)4HTCVL%_b78p>+gMqWjIbLY>QF&(MY_#77#7Z()~6B^*==HQ?wB_}5?B_k^* zuc~W3d1luP+nG~K?w#JT_vEny+t;m{wQNbAs(`SVu&}7Gkf@-#jkK%)hgqtPmbR9b zx|Xhqg`tjtv4xXYN=1I>w1x9PgEH`B^f^2(CMGf_I>g(_!^+N7LPAnfNvV}{o3p3-CMj~qYu;{4GQx0l)Qh|5Zfi;Iei2#ShJifWn2sY(lp za*H?xc{(~aI7@rFySWF(Mn}fxwl6)ftf3;eq-XNnc?;&xpS$E&=YP3B|CffwhWh!2 z`FJ?k*_vyMOG}DN$SFulD{1RzfX0~wr_arsyl3U1y~nSedHH0fvY5PVgS5GXsHl*H zxVVhCm93JRs;G#l5RXcDw2zl>L|}A8XjE2eLTO`L+q|8(A01fLRa29jm6cPNb>{!y z_W%69|1SuO3<~!T4RCg_voY6el;DyAHG-6k4AbUMYoFmWb5iD_h3j^2+_v}Ry$mTO z6$Mb%k`fn{l$Vl}mGp2@S5=l30hN-X4yk#mv7upc$#HQRm8HECCQqNUXv?8n?>^pM zH)(Zmvibb~zuW)I{r!I?BqBUAASl?`+RWV4L`qUpN>)KZPC;E;-(l*^Hc*$cv3Ab% z1&cOpJbuVWT2oC$Szf+D+C)NJQCVI}QQqHHMMYIjN@}BH?5?( zvuDPF#YQ6s6Ao-U%vqK8Hny`TM&2xVl=H8k#9d%E~FoE6U5OXj{2W zoYg+VYgR^8=hUflXKXoRr=qO^vQSA;R$5wFSxH$%SuxH@SyM}0QASo$LP}avSXSG+ zxTz*HyS%ogd&+`kYu4{rK7ZbiX&`>e^cB$*x-Z4eF|j zit;k@vT_PaO0tTUz78%C8O50q5!uO$(;Y{n_(hrZUXW)!W_G*}~XJ z-$++NQdUu3K~-Jb$iy@o)XnvoJ})n$FsGoXmIt*p(A%&cw9j2!D0 zR{GmG*xB0I`}h}on3@}^X{zd47-{Mm8JSsHn3|f~nHw4!XlkmdDl2OkXa}?|0G-ML z?flP4%y9ScbaZyo*HKm1(v*~vla*0WR94lqurHZ6t$BvwtO?;sPA;z9!6weO)^_$b zj^2e+XV0l}b91$Kbn}f1XiqS;&{0)aF^GxqGcz`^vUX^&6>zk-urk!q*VNR}(Xxo1 zGk01exQ^-mFZ*Lzeq@-Vhpn}Zk(R2mmbNTt50IjwvW9`BYvSyd87{LX1V=d7#ub`7 zIyb%TYl2!Q|8ss%QmT`ii=}~)wkoK=6&IINkds%`)Uj}Mub(@;afbf%`FWlp zmgdEtj!urCl-#&r#Uf?8v0#^xVqUrYTWw9#-0Fa=HbJ=gylxxgobaGc3?Q zD%?Lmw{hBxxzne?$8hC;&Yj(m6lQ5_ZEk3wt)Zx-swE|@q^cq>qi1U55HxjW;|zgm z^K-l%bkc+EoLxhsl4s4C)-*$N`n-yWjNF3i#@>mGlRUl6^mR29UYX|OX>MSorQ$JtX2Xnz>6|lX&73)F z*39YCpw)Nxe}$iOr_RfDaMQ`FGaHgmT3OqxBtVFur{`8j?T z-dXN$QJJ|@K|@EP)904tHuOxIJbn6#GEY|vJzZrryQwp0LlXdK_;J>R-~cmwH&^f}GB9o_vCr%heo z;pJvytf!$AHfK7_NYG&6^mzr|E~aMY#s&r&@=8i-isI6;T3Wh>X0{IgX|ozXeSWT+m6@5gmb#{jlB%4dx{Q*7x{j5tv!`!h_N;~(9J3|{CB)@7&z;^o zLuguKN6*xWz4cS_JUrdaO$@bSXTdzn^mE=c&`^3*fR&k^j*6m^yu6&WvVy#dhPHu~ zgPW&UT0JOUSR=B_E9Xt`pCMFS*VWqISXtE@?(OMdX{hHhWhTsF+903Km_D!3*H&L& zU0p#@NlsZ_7BrNgV`6UOW6j=GwRqJosHyo#Q@qK2`ZlcSHfUs2Yq8Pn%wI~0SQ1sVd+sVvG* zjf!cl4RUj|F}H4?GkqpB#6X5M&QP8;)z`wnP+d(?P98K~sH`ThqHkj3;O6NUlTkBg z>Vj1Gr%39{; z_RemBp~*>;X3cJ>n=^g#48ggriD@z3o_6M$*>1j0{?U_WAv>pOhT_a=$u?SA3i3+I zN^+n%I}3=a3Ovo+DLi1zRc$eV~}3di)>Q+*9o z6qJ-3<%FfA#8pi+Oie9pJpKKna!P9FO`AS_hQRCuXAe6w19k0!$e^ISI?%~YpcW`( z61jPX{PbB(&U)&qDvI)Qa#E6#VmcNECJr8M{*f_Zg*i=gLGu;UXLl#~+v;j+%3F73 zh89*%nmGe0Y+GjVOrKwGr6#YWq9iLRDb^stZ|!O9;O^xb5uKRUP&{=O#EiKuzD5Qr z%2F=XsmU!JbEZ#6GNTnV9UNn%p{%4RBMBPu78f^kGI#R!3yX|T%jtxe!9RV*>}koC zCR!>IR*muXRa22&(LO_NR)m3$qN0?zq=cx1q?D*(fRkrvXi7qAYE6CawAs_A&)}Or zYgU|ZfSIzSLq&B}|7>Izw1K9(XGfds%1Vigh)ak|h)Ib#rn~t@q@^TfXIFG&Pn$h` z#thz>vnEueIa{kZS9RvkoIVZ73+-S7A`Eop#XvnrQAr6YNxzJMh?wN0oT8kLp44fe z=?v~!b7yyEM!CDrY-?PEti59f`}CQ!L-n-9g&V{~MJ2=~B*nZc5@HgP(u*tVCUs{` zojrZ#44&!J=ggSeofB1)l0R+c6eR0ALDtU-*O3L;E-oe}E-EGDRTUi{o03)9+BR)k z;gng^XU*W6K4a#rg$=oN#h{5bL^|jK>7E^?DlH)@CMGT-A|@&&WL1+EmzYvmU*9=% zTE&#P)921$nKozsoO#Hu?w-Lpea74xZ8-^XQPBnwVKH$DA>*p-=@2_cu{(%zQFu8A|3Y`%T4uw&LN zlzH??GnA&ynO9>WCnYH=A}TB{DaOMi8PGFpa?ix+OSZiHFt2&y+*#9+XVE9m;G8~v z&eRMeP|9i$X%G_@;ZjPTvwF>}nF|+P`1)X8!^F9>(Uw!FfDTAYH4qb(5Em8|6_bz< zC@LBrGe=)RHn^bFeTksQbW~D{`~X* z{|}e9mG#Y;HFNs(Wk|Ya&rqEAL3{MRiQE_q5Y@e9806V*=vQ2!~+$aCv>~1Wb zGIhc1=}Tdne$AgTcb3YGne!IUn_L`VYAhupEXpq=DlRP{E+xVvrfw6SUlSeVliEIa z<`VcSmuY?TCp7f&Oq?;Pw<|fxMO|G^kh_6{kB65}SXfp?!^l1*Eumx5QrIGzKj%&@ zJ}qe!-rbLY;PGkfmr`SZ7&J$~Zir7LHbf!F7N*7B_X+wq$Lysihd z#s{?A6GH$0zYb}=&)JiUPKlj7b@uFqa|ieBKe2P^+D#kRZd|u=`Kp!68jdiWUU`b? zl9 zrp=z-*-$d$;DICiHg8xwXWojf>!-$>sESA##?()pJ$?G>sZ5h+L)Fg!sh>5mq-x6X z=g%+g-MDPolI6R0%uUk~;gb+m4w^b|=G4h+kg``Cly&Gpun>=MblKQDr<)+M;Sk-sx6EcC~G+O>&xRWZyr6pcK+an!>yXEd?I2}LRuL!W>2278AqaC{dwEJKf3eo-KU=)UOaj5X971fe}f>K zu#jQHg2_{MAgS(~Dm!gvee>lfhaY`@^ZCR3*LU81NMYa;5fBg(751GvWAZK}#XTU0 zOsSuK;nvwlFTT9{{`}>gXP4|51o#C6Ma9H|XHA>38%cFHNcDu04aaw#zjE!~$0yHj zo_o4ZoLP`hP(V~zGGgY`DSMDqcY##*mG3yb^W^R8S8hDIdGo-v9v%*UK7Iia0qM%w zQ>U~}Wt@T%ik(wsr_AVY-nV7zft#1l-F|xM#Q7IR%>4Yk{6fOQ@>TODw@+oBJ#*Uh zjY!sXOqHE7y|-ig=AHX?oH~8=%F(@7pF}VS2=Maq35klQgH9XaoHnVjwy$^k^r=&j z6Hq(Iwh2?VEZur=|Nc|w&mP@==8_kKATKXJzksk<@U$r%Q#q%_`b@5D?w>Y$`WhrV z+dy9HZ{E0i-HxMsPaZvdZrjd1cFcmje7phzA|ie>L0WmH%}U5QzOJjeuy^{jDJzk5 zw}Ny}D4#!b<%X?$cJAGMVCSI?#w^01jYEPWVxF_6PU)P=J7wDRkb=FZwy&8rp>NiV z6)4(er%x)HH+#;CwR;Zi-MM|kjg^X=g8ck^d;&t^+RZa3cTeS=GNU^@`pU^eFK*AN z?wqk4MYqhflu5H@E?ly9&%s^$S6$dC%`L#k&(FutFQQg8XL9dU-f5H4okDIL-TwH~ z<(|%2vq6#6hGatXRM{!B>RM(_p1Wkl+6|jGuiLzUpHGC3n~z^WNJJ`R_T;{)0#jyG zW@x2+xwikz%P(7-XH1(ieJb;mnKP%fBAL@PRc6ZUnv&_0X3So&bj8vI%QkF~;1(7X z;NuhE7Zh=sHF2uIjP4va&6=ktFJHO${bXG~=tx1ikI)K?fBUN1E#D&V6?H z(&cMkAJt5nHf1X7jE>Htjv2^CHB6PAGP|yNQtzax)27XsIeTW$!enk?Apt%O%=|c5cIzz6rh4W=xwlWm5m5C~jc^K3-lvejy>(*;A%Y6`Gb9s;8|f z?|)?NiuEfOEbnca2|Al$%FOO*@3)y{&6$j9-1M%($$kC(6Q|9XHfeJI^huJ!LW2A} zJiL5@a&@z&Oqt3xd3KqXskWw$c+RSZxonH)EGTQ5Ii+VR*OZyPP4C{$Q7)JRcc2)^ zfm7yGwDk2&o-lRlv{@4;_RVwW6lvgR=jGw$<97wQUu4Sc+)zg&O*I9ThJ`agNBd4H zo;qa?wMh;JS{mvyE;A=inmDnyyQ+VB_f+obGa4>^eYn*~Jr!Y~0?5FbrFAX+6Q@p_ zG-dLni9OSOxrF)od3kww1$f+MPo6$iWZL97TLW!PO=Y!;&hE~Jy1Md?8B;ny*>Y;r zrSA_OmdP7>fR2QKRDK{MXLOZLZta^oW5UEqlPC4}H_HkM@bUBT^6>G20&)6O;VH9n zEsTsbwbbMzYxB!WOAGVzXLL;En=*4^+QsizU+gtj)v%v54Hh$UKW9&xRZ!mDG1gTioit_g#J=v1+2I_*g8aNZJiPouDYGZf02x|r ztF33Csi9$z85|W866)z*IA?MTs6gm1zVq|)jaOH#l(f{GkxiX7qots!bwX=<|AhYj z2~Cat3jD%Cf_%KZ0zw*n)2DPyWtuW&YQqessZ*x3Ock9nyTHcC&_GXHTPxhh+u6;_ z#Uuihzy+tw?60}?`uL@rZz@IAwe%biR>=LFJvp_wqOq;7wRggVzP_f;KyG0{K_Px# zK0$GBke>xW`D*rzshu62Q)bMbHDkuq#wnt6a?MP&boKQOl|noWOl&PI^$lBQg3e-? zGHq5@*M+-#&z!t_L|sioUk@eV=e1{-6}R8UoGB6+rU6yTU$MR=9H;Zrp%t0 zU$=JamZPW6KPZt?SJyFeMzmd% z)osd@>9ZzO=XP&8{_f4|hu2T<>T9gZEv(9`n6$9mQdh^!SjRv+!BkyGTSH4t$)bPS zl$kRd3Y+FHTDx)Q!5d4|RJ67A4bu@BRQC5&pVIu6&Vu@uuKtdys%l9AAps#F0Resi zG0Vx*8m78WnKi4ou4T`MPyarDczx;k{tYXa&6_c!v$e6fyd=g#$3WLePggg_R7F!m zSy4qvt8v!MmI(i%uKopU*Kay_K21(j+sMo!2a(}qe@{+rN-wD|s_W?P?P%-_=MVwS zjtK~h2#Y4oncOhdeRgGb@w(Ta9zJ~e<@bw+*AMMlw`lg%zK)LEvTTp+B25E59b*IC zIDJJWHDx&kMcuL);fBWEu~q$ZmapHmXQr;EmcFrxLEdb5vF7owvA8b1xU#sZt*5KK zwZec)NKiRi%6UsS9=`nK{g;0~f4{kR?%<~7^QKPj z>B>$BbTGEiQqj`X)YS;ll8}{`0ZqTk%PVLY1!UAroHTp!5`P7K16?CCqwwjFy0`Pc z;J^Q$!aMVGN^8r?n_8=DYa{s^1Q~=I1OtRbgnVXBnc16L*WEvT?(Y3JUw-)g`RDf+ zcaH5^y?nvcNxf}336UOFj=D0cs_I&55!#ZnGO|+A5`rR9D%zIedF>Ns&R&wQq@iPA zZft4UI15@!cm7WY&4hNAmR7V>Ra7@u1qDl91~A|a`y zuICV1*3iAMQ%6Nx$H>&g$t4d|cz_D=&i_Kc|If~>$gHnvEorK(YAH^U;^S`+2ow?) z6c$#Wk`p*%>V*7EFGXP%CQd;SF;Ov5F(DoSW!t>|zLvs_%qUkwV>3-DWkpqW3t3Sy zNpTTD2}xNs9V`Fr;`)V^T8i2_T9$TpHtxOCVP4ev^M7S|adB34Yh_+ZLrGq+0Kb5M zkg$-Tu!xXObXsGaqcjf_2fvVnjI5l3OoObMjGT1@f6;&cKPQys)#etK*OldD zr)F623GfLBHi$?I35n|%>nVr{2?$Gwh)KyQ$SWu-$;r#h%1BE$NE(Rni(2I8`P-SA z=xNH!$jiw%Nb?H{3W!KZ%PFf`xQA5M+Y8AksTmlXT01*?x6Xt{B6z*e$E5O%^n#qi zoQ(98glH>XK|TQ?Vc`ZL9}(lU)Qr6RoK!n=Eh$-f4GlF_RShKt@X7{pNl6g_5l#O% z2Q76KMOir+1p`q~*hompE9>Z+M>J%~@`_6%<$j8N_ zW2`AJDJiR@!p+MoE+em^q~V_9EyyV%EH0^_tgUTm=@vV=3t`unxWvMeqOA1n zcy%t&Dh6Q@At4Eg__>o?rrJ-LJZ1Xq*^?%e1gR=0DM?9*i3ke|3JUNG@ba*7$;eBI z$*3rEaS2Mw$SB(+1WB{<3kr%#$SNvpXqqB^&*goKun5HmY>0~ZgkfQY2D zyu3z;Ys0K5-3VWN3-S&OPm2qS@(m0L@&pyiARC0lq{NcvPVSp(G=jP;KWwi(u<7HuC zWoPH&uL1SjK;=*eXwv;hR!BrtNJMyukFATXr?8*^C|m?Z#N?Gy zKrUCBGN-a2#o5`~z(7}9T~%3LR!%}(TvS59qFj=jmy4T=gOj^~i;I_+Ur0nk&btZJ zp@Wz$@bCZEpzttnKmR~qD@$u5W5EU?E`9-FK~X7r`Bab_45rK}ZJCr98R_d}Wnpfp zr>Cx@3|h4y&c~ysBFV`wz{AbO18Uat@bL*M#mt>CsUHz0U&Fj2ecXZrovrLloooaI zg+TR#popZbyzQiEJyVq@&nxXeR@d2Bm>2HtW^Ze2VrpWnCMv+q&Md6TX>7zJ#K+Cs z$j2ohC?qJM?%z6d=Hxzv;a|cdqJz9cU2U9=j1A32goFh71^M`d#ARhICr#^~YBpt7 z`@|P>XU?77*He*~671s_?xiiw%gM>b&MqLw!)0$OA|wFH4nl%LqT-@r!WzNNbEi*% zCm5lB|G)Tn`*?YKIojK5nCR$82nsa{unP){Ny`{dp4K&0WXjy~{P!CcuUorn;r!|C z!#bl%<#3Ur;9P)c+&Y3wC)B>J7`zJVR zzXZAZ_;`BwxjUE`YHP{y3k&n{gDN&rX~Rj=x~AGrn^bt`*v6gv4(!^va>?{X^U|bP zIeECaxVgBvIi$4t1-Y2?!ZajgKr1z66;}GsLo07LH;?%GMLQPNm1Y-qPMAG= z?wmQZ7D86~EOPU-x3u!GG1b%3RF>uy;OFNP6yg&QmX=8Y4KayKnU&MOb;<4>hb|s_ z^kAY0m#84WfItI}2^SwPj{uK`rl_Pi4>u1Ni-f0#nUSfJrK^*@dsIkZN_k7ujLp~X z>|4=ZRgs;Mk(HZv8nn_!?)U%sUe4C8c5V*FdfHklvb=o!pjZO`` z`FY)a6Q<0VyKe8bS0Ape=v&ztWdvU8Bk=eCSyxAUcRL3+BMlX0MJ0g-el8(lVL@RD zX(_WQ)7qx0O_^C=HDk)0`KwnQ+-}PwBOxv(1WMpMyrN>l0%C$rI${!H68zkaJPPcB zb~)L_(e5E}$>G_xt({Y5&7Hq=(Ta6DFWz{yJuPwOH}FcIFJ8_LR+dJ#CZ_6&@=Bt7 zf%t*;=o;-2Jl=a6ng=8e9Btg4lgaidd#T&(Rg#rvkq@*Q8 z1qFF|_yzgc1?3#Fs!Jkbb4%*m`sXZOxoXqmnKP!AruZ0_PMh-+vd|~Z$;?h)+uYPp zNlr#Vl1D&LR76-*R9s$NI~&yD^qDfNDkP<}w!Ue`F(os=CK}pHHao(g^poKoAE@t{B zhK5FJit@7Zvb_9)qQW9#;!<+*ifJJGB&N*Hj!%tGO3gl>C7`M(FR!E^Co3f;EvwWZ zX8_6t;TB4A5|UDq65_(3$tnp6Sp{)1(2`qeaeh87E_M!Xei;LuuxT@?*$s?X%v0pm9J9b5)#5W;cSD-dJ~1RrAXo)U-7e`OElV{8-Gt|}7)Y3EawXE?~P?4696jSu{vQv>)R8!Z|(A3b~&4F)HUR##6+c~g?I&ogd0TI#O0Lrz(WWw(|cXrwA6f4 z6?Am8b@kMY^C!-pHhE^9m5Gs&jcssb%q$;yHB|*E5ji`bm_TcLeSJeiQ!`CXbyYQ0 zIcYf!|E#)Mpz}LHg=qJG`JZz-qe8U|_0{F&rNkwqB&2wFKzTw`LRwkV5`284^W-_% zW=^WgX=a)_+S>ZM=JoTYPM$htW}TOlla+H&T3*#;do@jUc^Of;h^DlXxBwp?w+Iho zEkjFVHBCLM(B@flK_`d8?3+0&Bivk1M^#Z(NM1+-Ul&Xm4M zlP2^}oIQ8uv?PPMzE`Rb*O!R7`$ZV{i9_r6IMMv#N{ETcit-5wiAqSzDCuY#MuDv0n>;7WQN=o1Psb}Y zbJFxFt>BTUg~Z;(`=mxgyj&Ah2o+NO$5nc0}p+||=Nal+~vLwyZJSt%jE zSyPbBoH8fJOh-{cSwTTwQb<%pLWEa9P+D3>URguaJaNX9hN)cB`$JU14crr}W>4;# zDm=ZQp|`7VQvc$9TN5o+MR`H5Ig?@L>48RDr%iM;R8Ub+R8Wx?5tR@X6W|pSmsU_y z)6zHgX`DF)bZ%&@si$L1{p=}SQ$?rDXszh)?wQm#X^yLjk%pR_q+HXiDYK9~J$1^Q zOk*`gMJXu>(7Fg=F-bvD5lIppEM2TFl~@er%st&>!>L!1KRT_DkLf_Bn>Lv z6qPiz&5WE9`e#g;Hq9=)xN~~nRG}#|TXGsJ%F5y+TT85r^fZ;EtmZ)d2y#`!RMjan zL$ws-q{PJqMMU`pg~jDWM5GinwY5!*%`-EmPn|p`MZaM76wuV&?8>yf)TB^1&-yY; zU2RQe&6b%{W+EBZI8}MtWJhH=ISFxLAz?m#Awe+-5pg+X4J|!mGxy}GnUm(GnN6BL zd8$BTLPChYory_uw3fcErfDqPdmuM8PF0>fJ4a6*v@JkHP(VmnL_k_XTuNC*OIy#< zJ|tnn^cl4^Gp9_RDmc3#DBRP~NK-L7TGvF&GGxLuWal(bRhSZ|B`GB=Bq}N*1nO2P zNysXxsA-tlI@o1pOq((jG^j5)WoD|Qy}qV~yi9?Io|$EO|8!(Snx=A2nLfo?R!mq_ zM6f|fn4h0VTuD+%Nmbp*!rV13zjF5EDbuG4%#760*HD%dlSy*2u}Z3)K4k_{xHM0d zpEA9{L`qUzR9G0)UgzhLRgqKF(AT$c^|DXSs-HDw+El?QGdiPf)nuil1$CRF98=2s zry-lsGF5)c+-e;OVG%JAK>-0ieqKIpGkGmN16>!7!0@VqNz=h*OrO={C?_u_&SzN| z8r0A>W6E?SceGCBoIEQ+0km*JfUkj9h>w>?QAbtR(8$)=H$1)-Vg~<|DKn--sVK^b zb8A=mmsL(ec18PCxfwyy(jvnAJiMT>0e)^pYb_&t`#|5Y@ba3R$sqgqrc9q1IRvKEE!bA|EuXhH!ir$o5%5;{1F(JiNR-JbXL?Y=*@VfuWJPbv3P1CY4Q^HD&Hp zmdUf`%$kGj>h7uRQ>Mx+^LOIkoELV zRhT@tMn{OBmz#%+8x&5Q64~id`DHatEp3zLH#JU~jyBt_FlEM69|2xo9xiTh+;H(_ z)C(lG# zC@^uV;*|MK9^$-wygc08JUsk-9F`He-Hml^JyYhdySl%ib^3Ia8T3h0l_t-eQ>ib& zC&0tY%gx8n$HpY!)i%AqqkGEyHBVm8Y3Q3ZeG2mY`Q)j}Q>M(CoUG2x!^a1j#Ny-U zVUmoTzHG&`$#do${dj*)ZQraJXe%jHrc9eVDM5{klNUVi#m~>lB50Yva{tB!^XKmS za%)Om$IO{irXWw5PoK&)dD^^*$y$8uJOTm@d}6$O+$@Yjo>g1V9X)dR{l801SxwVs zPoJ_1DcQ`N$~}47+=~Pn$cZHrYvD032YvJbe7@3=CXS1`*9uAN+r{yRLB3l({peEQM>DJ9X}Km8sKa zFPt->z)MG6fSZ$>or_mMSb&e8laWJO%PlF_)5$ThW!AJM@Z~L&x)${Cb#?bm?eA)f zkFiyd7iD8=VBlkBVdG%u6OmFjicW}a>0Jt2J@e=6$;GE+PMkb_`r?&y$4?%aKWFxw zSuf7p4GwN0yKD=@Ml%Afh-Z?Yt z0~L7K`FN#V8ai7V8`dJJp9WT6omDXR=-Fc%=FVO)Yr>>OGZHj-n7M^`#2oTLCxxy@ zQa`m(uA#jqqh{0DYlk;)+Pr4b+?fj&r${k!3JCM_TIIL3Hf%ytJ-Jc7p|v@+X!E^u z`_CQUdvMFTMH?2S$ue^C3kY#5M7FdwG;BsvKB-ZpsjaYR)%_bsj-EJk_SE4mo3}OV zGO;)Cv+?rV7j-r?Y(-K%p;4rvxiPKz=#>NePoKDW<^18jCp(RpSsHlR1h`}(TUzV4 zBdP9h6lrLyiJfs}?~YR^&s;urWZ(WUrJu4LeX2%e3ZZZalDf|Jmbb zPoFxr?ewik25wM^$*b5;I~>2v1KTDfZTrlkuuA8=>j z;pXJv;^E_s=&oyP$M{WlPq~oHN^yftRz9i-nil zskN!Ak-Oc`Yx(4|qU5UPriPVBT3bQkU7Oli(?4a#yxH^SEu6iiQ=XX*WG6Qtk9AvP zLw6&0tBu#Zm9wX`*HyPRuRzf(+gz8}T;13^Y2Lzlvu91--X+A&!^OqP$<50rU)0*r z-N@U}Qf_FzcJ-oD2Rd`go0p^L7HSDBDX(sCpEP&zoP~W`=kRlIb2f3Y@JM8}*Y`Ja zdnuc3UNPgq`2+Q(t+PRK(}twGxly*EEjOnqzqPAx;*@FACrxYT;^GGd7ATzE+Z*~C z`5Ri3ZN=ShY+tbI`0Ztd%}p~JnHpMJkWFaLq}9> zWHH+Pa{`DOI5+G)8#^m?Qj#test6~HFP8dN5Lby}xM@K`0gNJWWY)p1>UV2QVU#=?) zFArBECkr=M7|7qE4Q+|aGP3fLQc`kOx&{Wi`kHEq?F}uB0-(C$#M2F%k8f2LlR-7K zy}8KIGdMOlI3YDLIoZcMSCpHFhpPc(s7y_BXCp^LLt{%zb7Mn8OCxhbd%SXkJRj&Z z8GUUnbq!5TS$9zC5o~C!&fIls)t0TtBYC8xW%QAq-QMV96uAM2@&C*PN4Y@PVl@O4>(y0 zH8i!?6&J+kRumSdmgUA}RhQQ_wCOgqCMYXP%g8FoN!Vz~$jiyeNQ-;5HZ(RgwAII^ zPMSGm*~$$Ef`p_bW#!Bf*2#YFjka{O3Jr)z%ZmsL^9wcQ-~~YPQ}TE!)#rR8M9K>-YDnTh;tv39oc^s)Bw4vF=1b?_77=H=#T;%4I2ZD?$C zZ)k0+&dFPO@80e6Cl9S%vS3PIZ*@*id~{@ZLUI6j&Rt$s#!gvWQWCVYU%sHVrN~X! zE-pGGs@rD#?jf@Js`l(+1*xvhnJg&8`NjyRjh7m zXsHj+Y$|J=x@6PAlNYXByn1Z++Qrj)nhUdn11ybo)f5$_#igWVWTXtGg+xRfge65l zvj~z3I_4fR>Gjr$PcN0$#HxDl_ zr%h{pYjtFJaBODnyan41pS^kh()q(Xm(S_%Z7WEP@Uk*C)Ku1x5tfjYl9n`=1t0w& zEXd0*BrdJ2XYCzd-Q4UcDkUqYq@=8p-v+lZ?ce_|_K{8w&R$L~PM(fduBx28JY2kd zJY3v-oS~g<89}+8-Z^y>*I#=0{LQa8FNj107{~84+;_DH#c4 zNdX~20YM=?e$WyF6@4eqsFJQ&5g9pI1tm2VlXh?w*ZE)Q@BgD_-WDFdJ`T1{_Rdx| zl3d)4Jb`??Jp2ODU1<(UK~WX`^SbiV9ko>zDjyqsLD^tDtJmBhp( zB&4LY#P|e2Q;wk7bP0JiJ$u)vhHM!L8CeBIMRnEawgyNO1iZv&vX!fao2#F_hqJS% zjhP@9R|B^gFAp~lw`60qUUF(?NR*`nHvr&m$ltBqpV(VQA@);le8>B`q(ns-~st-U10w@OqoC-tJZ|R`w3A z4i*+BW{RMBV{TsF1|D&K83`E)eqJsf0noAlF;Owl-gHqBkwzgyK|UUSWj8AwH6|d)yUnz!^*|Yq0!da(9Y4(-q4&M zB`zQ=EGogp#my}sBrG8sc$OF0UE;r4;69g)-^Ub*4H;Swsf}CCPrEq$cafyiirt>Mk#qgBV=5hTvGDV zqJo0L;u0L3+ycU)5@J%ij>g>V{ComJV&XC~@+vAm-B7zgKC?5lwX?M}wJUXnmJk*b7ZL(3MQ`9~;8x*e;Sd%T;un?>XJ_XT z6c!d!wJ{fB;pXMx6BHH`mzGsg@&m|svrjDwS3Kt#n*TbPNJ1Jqj+5^0cN zRn~B8ZEEO3@{6f~p0SmIj;^+*rk0^7cLNtEI4$yV1$EZdb=H0`YN&bkz4Z z8Z@+J+2|=MN=b@~i;4*e@`2jWtSqcb#(bR2%q*;|Y;5f8po)lJNJv~(J-5BS7h(2S zdo6u^9X%Z_H3em54c-P&u<&s6@Cxzx?57vxcK z6X4`z=U``JYh;J?9R&n!i`p9cphnC6`~Ss6S65R@Q&U4;PF_x)2eiZil&koK_<}m> zdm9ZJ+EX(VjSLO6)K!#~6y!jYA0omcg8ZDE;^KlF+}s@O>>Qk&9H16AuY_-BOI<(I zY>|KezgVanXsT&zDJdw(sVIUR%+tWl#=|EdXi(qO)5u@nnOr&F&ELbp#z0G5O-;2y zQ9wb0pPQYHnMZ<6R+fvGi<7f~3zB^VrJagfT2WkXsbi?4rmdo&BqJxQ#K!}wSh+a4 zc?J2j>zle8MH||RDz9WDr6mRVI@_9St7#i)Negi{vT?9-i*T|lDS!ru8M(O|c)39v zVR+=+i#wVc8en-#_}~Ap=4zk=GZYmRq-14e1bKM48@SlHxp@V6H5;0`8bum9GGgzv z6*V?gIrc+vMaK&vGa-YaIna#3-WX>5QM z79#)tf3sE5)=*PZS5}mfm68(X;^ET0xYY6#uGuyexnS(DqUN@KE0 zy#-l0K#R`U**Vw*q_}z5n4}D(gg`STLLy=kBEq6#5>g6|aj_+hoo$WKO045Q+rR(c ztW-5M8r0a8<)mdLq(nKJKwjbE;Zba8>TDEk=m>4zRMp$JYRc3Ze{~j|JU|k z;NSo6E-Gpoib{$~(x3x8MH)fl7F?iwBa+?P(9tN`Tp2p6Vfw5^yBE%wv$9%+fs>D$ zlZ&H)U5kU0i&I#PSAd6|orPUiMP5cmRz_A%PF`MFOG915*t=+2LuyQ9Vp(l_dqW3P zYu}H~|AK%1&yd$tQB+WolaZ8=5EBHgW8>roRVe~|;T`qujRFmAQ7PSJQ~GDGn|E+$ z0UsNGBab;32M0SB7biETq!hoP00%n<8w(yuDO$^ds@%t?MwQ~ zQ<9@2BBEl$PJ$NpaQ*&2T|q-hRZ&GjszHKLOt_J&ft#I+hewz{0A!s{Q@vMuPE}`3 z%fy9yU77hovjjZcT$~(SeB4~T+)AAXC=pgMq`mxqs6Rk}eyQxIf72PZqXri+!mg{H2NslJt~S735c zS!HKyfB(GA8&55D_G|z8|6e<3E#Vh4WmP2wS$SzG(E1usSSOB}5}pvzut<^zbetMLKX?|QiC<5E$3%f2RMGH&vLg={8@I5khmXC1iM73(Z&+qg zZGC@5eolI%zm8mLOXm;pvYsD)N=nMI(sI(0A|gVf0-Ri+ISD>KeqjlzSWxd$xuG@3 z#MHsn-8+7oIWo0=QJqum033bobb<)vjML_n<;Ay9jjjZc7INJP{K)Fx_AY|!R3P>o6A zl@t>dmuL`U6A*Q@RT7tMkkFS@ka1KI69+9u1~1G7t@9R>6cLvY5fKs=5D)}y;S&&& z(2H+sZfFAACi}C^R9j9#MpjB3)T|KV<>cn!;pXM#6A%_vt!Zj)v}*F!S5r~2Ns^Y3 z6c-hjloS=y&FqO+mXZdo4^&d{RudH$5fBs*5f>E{5fuaN&K8r9k`NOW5fW+?U=Hzsi^83>gSkEaMcN*V@p~1ofcE zkIqUf9T`~}abaQ5!gm2d4o*-jpItymTv9a`bP=FaLwlT}nuM6066pL}1!>K^&c=qu zhL(IoH4SBTJ#$;9bS()fNl_s_Vei7IL{D=AEp>f$c_~>%1xYDI9rwbv4)}Pd@Q=2( z5FI61X)zH|0e&HVUO`?io+dsvK`}|maM0y-f(`A_N^-(VrsDDna2=8faZ>rCWP4KfY)HAoVjEc%_Y-+8q2PHYsB#iKn_U2qe zRZ!Fl3kvY_@(BoW^MH2TaSMrvN|-h^H3~Ge$0*APnrTSM$f|32wzf7jHOe=%rD++M znAy8{hnDKgD}Yv}^8|F%H8eK0w6!0!)wgxDG{NdPP#Wo|Z?sXBmJpW|6%gPP z;NutJ=H%mR;Aa;W5tmfR2MxOLH?+qnN{L&^%gHJm`nEJRv@{Ag)j1g0ICumFL^m2M zD2R)Q3G3G;D*_8|%ji8V+w zxq|NY<7;S-(-c=Vla_6v!MO=@(Nm6H$=7U0)yXqpX;cF?e5 zbFGoQn549nh>#Fy_p$&FXkt+a)HzjD2RoXpsoIp^SVu<7I;XRvQM93@$kEL=AUrmv zHBL%iQcOgM%dxEi*~EtSXi%9W1~QR{kB^^^lbaWmqe1r?c!Gj~tEtvP%t&6-DyO}^ zvr(j>HN+**KQu0)B2!*jT2f4u%eEbEpx%#;`o^X@0~t|J))W!s<>Tk&=jP%U5E2!a zl$Mj%DF7wTrdoRiEj5e0wuUaS3ta;Pf@4Bs%Qcl0q$ETI#0py*W z(5D4%8WU(3wNb6vRvxm#lZTs^hnt_57qq}z60}>yG^?edv9Z~~#3iY{zOPZJIo8SB z!_(EqE;hUmz{AZa$Oq~d%E&1wY1(A9)OUvIHZ)Ca6mDqBwKUPwR#lWUH<4A4m6q{oMl*@C zzCA`#L{Na2hqr-8jF*QCvl!BO)oV=Wxx{khEKw}fw zjHb3c10fLs0WMWXJ^jeymWJs_?r3e4Z)o=t1nn&awSqXgI5|aSBxK|iR8_Tf&4R)j zn;WJz@`Fa7Jtahh1UaRhH3O1ReA3=1*XqJA1nP8hadLw8N{A@SC@8DwXzJ>^dWJMK zH%xEjYiMfq(^D4V=aRE^j;?A(c0n6x?z+`kNSFsS=FbHhF6NXqkW*CG)zL9BwuugH z0Hp+;=9**=6$u^%$Efg@hG|GXXb0(UbL1D|;RH>RaDqZY)kslQLtD$pLO(Fuud%sd zMk7x{Q(IeUfVr|{PJC7uvi^=n_J+nb2R=bg_6ANy4$cNH0S*-#Ee#D#LknAn#4L~x zW;Sv+)VDM>6bD-QSj0C>MY6uLk*%Sz)rpUrtAV4DQ;36`Rm(wDLqpfd+SenaJgTm_ zVOAq2=;qVz{P5VgdStC#ja&_lt*-oB+#Kv2T#X!p+-x%LMw&Xh=FTo5d8JA9tqpS< zS?XIl+S`#G+}+62(Ab_X#sL~b;N;+F;N)kO@ikU6vT$?@49#lD%59z3*oYEFy^U-Q zojFQeT%3*UqM%4(6Y{jswsLd}4vQ#k&(CjYMwvM8Yh-I^X|(6yZ#&abXYtZql0XJ>0@Y7UoT<80s%X9sn5+1M@p1Fg)=eG*~|CQhqPuWv<} z^PkwL*w9&M&Bw;g3GN_saj|H)`9}wOMVayHbr)`!TmvU71YaEO7Xycqc1E8Chf z3mR*d-nlv{v$CzF0d-LXXG3FWU633L8%T>dCl@z61D9q@@7%unwwk%muC`{DwzV}h zAkTnLZ&ayo>Z}VLMeLP{|+8&&I@ zI%;FAg_xPy!P%CJn~jM<(5-sW-3wQL{=YLRJHD!|si^^Z(t38IN_}HTdrqji7$+M$ z2e{A3!^+6OEUfHU-F@cItp(}Pb&VY@%itDuHO_5TX=-Zk>8MSxSC!*pV`FD!=i=e# z;MATs+c#S(Vjw_4PH?rEwOblJp1w@GYE-oW{9r>z{x4 z3dzY}^6$-fh_lG>5wTjp*9$Vs=B|4C8*JzQf4_hK{`KSgzyI$R`tuNL3c2c-Ko=(J z)tvy};QHs+pMU>Aw}Srr^Y8zY3USc=isTwhls=H{+|dXA{{R2~H}rN)&?T|I|NVbd z%+0{S1iMg`D8oor1iA`RzT?^d|Nnmf1^F5h{Gcl_KUHusfa8T^GfB|E%*Y_s`vrVo zGt_1f_3!upKMUm;7{M3Al3*ICN|~4$c)LG=ybiJzj{pAof7gWpoFPaxkrefeEDVg% zPyYY=jTG)6H~jhkcQO|PGp#cn6AOdZp8tQ*ZU6uK|65N6P@*KoFQltwW?;(v2EBC` zCqg_D2({{yoXPXGP)D~t|d&&0&gg&Fhz|Ns5>(*t}3J0whLl>wQUm{vkA z35I(d%=r87ryBz+#BL}_i%iJG$h`jlFSI-lD)ImQ@j|kmiNOX`{8POQWMX96_#bqG zJ=o7k#NYp6NY*njF} z85Ux-{?Gq8pi9c3@eZb#7&e2F$GOF@caMw-=F{4xQzj&P?qyp?f>`xg)kOBL7mOW z!1n~%{=ff^GNP$wWpD&J@z?(vhj@4t@&5GB+6{yz~p0vMSY!hZb&cU}L_MwWnk9KrzA zJ0Mk1TM^Xn|MT!@V_;xn(D{WN0ssC#L@Fv7!EFR^Q|izEjc5@Au^B=#GHgW-Muh$U z{`=y!pP9iOp%}#a_um7qfsvV^1(fnZ;(z~NW5wwJCI(ed8Ud+8#ee_bMaj{OjLa;| zjLfXeut6_o20vu)|NsB{KfIu4VPu#AYM_9P{QLhVMgs!kcW~xwM2SVP4iNGCe zpmp{cSy-7FnHd66?f>`x4J5xXF*7i)fLs6n&;J#u2@`HPDD`pN#gY2{nZa~1GlLu| zY@a)MU0+G04nG^%09FR`f2a=l^B+_Xf<{H0&m+a@um9}`pMbR@2yit9@-905`~N1i z)&Mp1m5LT#|M(lC=J&gs8#;;ug_sy@;58sb=Fk643`{Ic44TXSB3b|c_y0ni_Omc_ z;tKvAuqGxZMh2ZZ;HJ^PKfnL{{te43Zzd@QLac?8|Ng(@XJ8O%|AN|s`uE=po-PqS z2eTL%ST0}<{{Mgf$ARo;W@K_&`}6<*--scgfB*je`2}*uucv?DR)ZP;{{J;%NPP_& zAp-Lu#J~SvWpE{bMh4k$sBs9<^B&44VA= z|AY&d{pe*F%%J~&|KH(YU}9nLc!Omm0ICW+6bNM_sK2lXf(iZpKLJle26vp|Ns79$-pGE?LR1Y|LiJ=sGf<>_z%=z{IrX=p953-&CpZz6R>}OzM z(0PPC!oXG{5&!=DKd*+f+5_7T%74VhKSTpFgVHS`z5nn3JQf1J$8SG8{X?t#Sn}L-XqzNC!9sZ#Fk22y3ihV}*1K3MTG}}R~(qC2 zMh1pM*dz4s|6^#Kc}9lWq*(tS)Cm5!J70wf)OBITu}%SJ^Y7pPH#{h12QxzmY1aS$ z5AFo~JW*#3PDm^)OpHuS;GPsBEKtXgkdo2A{~uAutQZ+~V-G1LO_S!jW-2bL;TD22i31vV!=3=q z#%!1v7GZZNA^Sn{(0SB9Uq1i%zW~|$42%rIpRwEj`~M6i|1&YD|G=&eWIhoX)OGy- z_y6}~ltjhA$iQ|Ddx84z|6`<16VhNa5tdlGL`^#Bm)zJHmEU<>@QHNLgo`-|NH&_XB}v66=EQq zWMmNfggs(^|3{R-EDY(Wt_1Z7W-UM!!{>s(zyIH`z&2Tjy9E6A{}DU^urM?u4K@D< z^>8;EGZbNoIc)7S&=k%8>4F$x4-Nzth6XIgfGZG03Bbs(4au*6|NY--3!a0-7INS< zI`FXg_YYJA{kay5t1@I_F#dzh7|>|iBxq_yt)_nezvm8GdV{U)`0WFz!2AFI@9*FL z{{8*^8=OWz&bDG&-+z62K|_X1n~dQ|22>{03(AWRLS2z|C3o#B`$c#ff`Z24ZsdyWDtd9 z(m(%y#UPR~Bf|z%jnHv+Mn*=+LO~{07JNR3x`CO&1=SmHC;a|D3mgHA4E*mvO8)$R z?ToOV0n{WwsT6-BkFzu4XmUVpMWPrPcB1+N=74|yKg)ya3q}U+m;e9$|MUNvDkSP5 zni(0m9-ySaU;i6G@eh$Do@8e51^EtUD-!+te;3F?Mh3Q9pyt#a1rQHmFf&@^@#p_) zkaH0Vh-U4>834+lCL{yH(f_~yKi5XJo`H!$6P7Q)j{f)mD=4oKZ7WP6GlM72007UN zFflCo|M!Chn)RS8fZ8hgi)9K7W(hu;iD3;|RtEbAlm-5pGB7cKs{j9y7}hf|Gen^T z+^_$wn2PXO43%SI(8fLr{QuAYoeYc&tPGX^H-WPPR0EV^WMDss?0`T24}dy#P+1Zv zW(FLSApigTPh?1h)o%qE^J)pW5^zX20yqOd?wAn z%8L ze}-!80r3CN{|p8e?7j!NiIpJ%)YpTA7;>WpBuN5hV%UKz4}3Sr)^viI4ayv#Mh3_Q z|Nj36wM<}Aq|i(ZlJ~HOz~BGZh4FRL$g#E{!27mvbi6!^LO=M(dVD0<|S+4r~e&;LWX(<(?QK8#+WL4qGt3gB5_12zz} zRNHF8>;M1%|M*X45x~H}z{tcf8J3dZc7qxJ{{4@^mkAia?GoXNtH1vL{|=wG1v`xt zf{}@#50RU}HbaTOe?d!(@FYS824)rp21bpF)Bhi+lbZ)XJ+x|Y{|0I;g8KLOe+8kH zc1+-Ad0@9S+4h4R5d9l#D*X57{~RWK*#H#Kj1(`7Wn^J+ev35`{QLcXGcW!`0CoZk zbgK%8Lk?zPkUfZf-tqVU+ZGHgkaj&eCKIWLnSr(YH`XBd^Z!RG?YAC)=AE4%W1V;Y z`{(~g8Bq6+NUxBojFFjvfATNz03sp>fLZ_k{QsEDOeiCeX)TgAW+n#n6BxTMKu-Ac z_y19I21aHYCj&-i2By$!|Ns4k^yoo0L-F4~|9{O>W?)1cOGR=#b(qXd4D4w)LA!6z zM^*m({r_XS31ptpu?|EuP)DTDp4oD#Y40s8JQt_ zFBv&_WsE)jqT=F1eVnv~c~FM)i8hTag^Y|$tkA(FB>mXLkff>2WCZVkWoBk!VP*!c z1jO4SqPFFu=8c8`wL%~>*}#H<(eM-l0|QT1fPw-qS34I21A|CfNo7G+fsq2p9w_af zRbZ6Cz`!7unUPWi8Q@`LU|?Zq2dQD;PtMONf@A{$1_lPDc zIk>9GDlkg^@BcrL!3bmkND_oWY!C*qK^Vja4REkCGQ!dS6b5AmM(3i`#FG3XD+RB_ z;(*j5g|O73;>`R!1w%bc0|Pz7|3M6(ZOM!btSl@HjQ?X8gdH3_JOYr>|5yeQMy#St zSVfsJMJF(Tf{W3hlY#O7&#lLoynB|C6aPg1V$CJ4fS}Spg?slbUw!_c zv}Di4I^Q$L?_XJPrH+Hg@F3^An;ubDGM-%D7-z8lep3Y7h7UHr9h^nMy3+S}Z!9|d z_`f4hfZD(0sk7q*%kP-??_TJ6w_?#t#yrWhE(=_B!eu|obVdFb>6DngXLYlN^g=t0 z^>Y?o4|n3?@;`p7*`9^-=OPBl>PbG&-ELfa+s?6l(M0xdQxZd(_RluD+RT;3d}%_C z2j5jT=fkg+synq9v;SRg{O+bR&ug!=Sg`P=((RL-yX@jsE+-zm^iVXU$!~$9Mf=PJ z?{+>;D?0LfN%`)S51mt@PCPugx&HHV%TwMun}2@U@m*1+w|7cl*x9!~!&G0V`P~$j zR+;4YpmW;8-HX4zNjh@nQ)NEyiIUCA?+Pu_1IfsRX)HmBt%-T|N)nLD~Y4GMx3*MZY zr@^~xUA3KG`{(yJ1t$DaQ$U(%Iii@4jBu$<^KdhE4no+sp;$H$S`QJ+q(D@oa>+T^?7uk?cOLYe!sRDUk^KP%y)JFlvVF?^f! zTvKl5$^Ns>%v4_Y(KT|;?InGRuP%P5l8v)-d%)<#v?akPT_p2uw`9%^7GcX(eN~(b z%I1r_dau5FN4a{UWBHQI`>n6dQv(b>otV-vH@A}K;j`C4EDZH&)vsr(Kf7mWckX+! zPfCV<$K~RCzZTWVzcTVTn5D77wI}7uWL4{~g`MBMKS#Xe+Twq;+f_#N=$dacg4|ZU z-8S)Q^6fMCm(4zJQk&;&&2`|T{L<}8Yudb?A3QB)_f+Ry<^6|y#foIiu0F6#UUqDE z&iwO-uFh{=D&N}CaoU>eul*6V&E79t=e|?qlG%Ip)Wf$N7pC>5?R{QyL*H!MQqHb- z?CEFb8<*@5Hx-^eA$zTadiK5a>#qDww^?*wrgiOGsXX7FDWvZ&yIII>K}nCqroY?e zZB2K)&3}~MG|TpLu5E0@`o0NIwtYG3{QCHv=nW6vNX0$t;A`w$cH`uEja?VM1-eDI zsR()<$o$@86sEOMY=5!7uYx~-b&*%d4=hATG0#ke%SO-I{Q@S zepR`axNEkePq#Z2ojjp0GxP4brLW9pyf{^^ZD6E${M5wRC#oV}XKu_nFZJs5O51;J zz5nAjyK-{OnfCXK(o&Oi{qFj2em+Q;E%5v4=3PflNO%?QK4aS7VeR|MNhDwA!l9_H z3$r%l%XIfmOMT2|oN_ikoTDOZ50ByYxi6&ZTm5bxF^RDK5I#3&ul}m&?7f@{dKWpj zzN?gy6%l+ZbT{eG^qxJAFI?kPJ=mD+mVGs?H_eYVuF<*FZo#@Kb$vs@WDZ?39d@-f zQ$;0vf2rsAmAhOEuy?bSZvCPYBqA2NL+G#;6UWwTJ*m>Y&~@KRpaUhOpi7<8cMIYyZ6a%)xN)9WBbeU>VKK3o}QT* z)wQmw>AQK;u3yJ*H(bjI$@=f6|D{4Mfax9EN%v)JkK-;%75g`FS+&H{zdZ-AP@R-I;&>(CzuHE9F~zI!Zs_ zRH0`_1m|v=@Mt!Val?$1;P6AAIaZhmbZqxL8Dexa#8KwAhS}k{zl9zIZ|k?&p0rf= z=@Sw4JF07H{jIwb6hD`J{}a&9zWa9dsXU*kxp=G46uw(|Zz?5wCGsMsEpP0p zuf8%tb(Y+s4gG76erNc$Omm}?o>+qB_r!yhHckhO!hJV?(3$*i$%>uUpL{>wouxEm zW$<^2ZSsqGey^%;Iem`9e9q^hO^=J7+t1`zs7vVmBh-8{F+TQF-g4RQ(+3))e1F-V zow6@(>(w8JH>}PpbO`Q|e6w2Srs8_0#J@=#r*G@p>F(!v!CmOOXR7#q<@gl2`~oIR z#_R9rXeG#>@%CnX$NuQ#&0ARw%dHxUdJEgU6}~-Hp62vYSNNUzpC`RDHP6jn=wZWp zHf(46#@H#GVe{|4*vocnk^DXJo`2fg{z^qm(3)K3W)jOErmgzt6!(4D zdCXNxto5wJlwgtawVTCn8QzulVX#~HUA^Q>I9u!5=gzkRXH_j!$71EVbC#iT}>%^)2DEyg%cg&UV!s+UF15n%}yZf7_9c<5$9M zC%$2g65fCFaL%r^EsEtA&+r|4nIOvX^j785Pu2TFdydWS(7v@|^RMq^rOJz19gAnJ&^^9h|Hf%Ij+@Kpe!0isw?m!v?v?%zW^43M zTeV*;3wiMEd66pL8-AlxYa&nW;JSHRe}U1f)% z%HEowf8T}Kn;hhP)y^46+&9p_=Ad!;+o|TqFFDw>9UOTZz8*ZUZS?b4YLm=_xaHAL zcIzCv@kVBC!Y$Xl^Ai(OrY~M}VMoKr+T)!vu;bB&p87Oj1FSGCu^`}q5WbvbqX65CiW>29c4H~Yl?JjuM-+6OFOCvHBM z__c%KxTCyr^YjNrY%Gd9!>6>SoAzox&N=$}p50YlYt8*t6}wc_zWiaHr}rS>qquX_ zFZRG~=RVH9=rikt*CMf!v$Ott2kY7WRho9UTs7h0^?OFYpEb#T46$s{<490_d+KU+ zn%mR;Gj3gC)ip^M_~E2>{rL5Kj&03}%^MuM_51HE-t^V7sW1c(q!su>%HDyw{#W%%?q8iU-Kx_ssqR7a5(;ST`?sytMY}* zwh2DF#177Ett{UbV^TR|!s=72G6H;_N3nXwm`_+t zZhK_?7r(3Qt@DHKK3HK;ywK$EMc<~wU9nI0C{(Opzv5Hz{_uhg@~gQ1*D0puA31%$ zHSG9GW|nog&qREeI}xEMoVn$txSG!Vis`qnByQOp( zv%9oH&b;4`{+$u-j(KyLHFVMg`HR!f9AcWPdZoF1(W_sUlcf^GG-ki8o8q>1!kz?s z@2XjO3}^K#dutzuSS?B6jI{eLGO1cB^Tzxa+8%mb*)uj)cjrIaT_PSk+5PIeV{7Is z{I_1*U$ve6JEozs*1vVlijaM6VB(d`~;{?3qCC^0t&S-Jbt>Rys{l zc#!3pw!GfYymI^Bg@;8h9((d(LuM_5>+>}apR^_DdZ*l$F|@j7dHSki$S}6MI(CX$FmzVBZ+P3YBWnH4m#)%Cnan{v)&lV_mWvt=Pjmw-hIO9I+wo^dnXc{T5OdO>S;-jQnOQ`dLJPqGS4 z-rkq?QuOYmn9tmzp)39;$mgmS@-Q*<6de+oQ7hs8$bQ%2H{1P04AyKt@a0aejP&u= zcRHC~Crs6s1WSe-n4@)+b^kK?&vi1{2V3_UXFc#)aQaG$B1hVEt-UKITPEM_zIU!w zV|(EZ@n1_4TKm`Q?X7O?|$v1?{Y z?ptWFhdW@(nq5Xq1lDA3?eSQ5P5A|bU}sLbTe`W#&)4B%^%`wYOhaeaco)@g^kcT@ zjVSs)OW5zsF5NPxS_3Go5 z-+Wd7S4Yo>MHd5qZ#0^9Ls;*F?z_(0a+hM{4lMRs&AIy9H0HTnmzSS;bmEo#<4~QX zo`7A>CsKHY9fQ)3&6E88D*H<7EWeN}mq=%RQwuitTX%PAiC!|8-`Cs7C8geY|Ak~+ z$%o1Azhh1_wB~$WwDq29?_GxV1=+hl3Mc#ze|%Tcpr^BF(VEsXRcCr0>^iS-x64aw z-o>3WIg=iGBxJcoZhz1Z@HL=vg=L9?imHYn9J&BUeIHfR#Kkh z?s1+;ATw-3X)?=_L%(|3J#H^Eu3Wpqku6a9a_-f&7iDwqo>tMad;d)A$y~iMd+pzX zy}}bF)lBROtlb-OQ0LP8ADezHS!HI*<(m?D*HA)&v+cWVnSJ=uw>=+!ESSxF{>+jy z3I~I0w+meIHH?4OQXQ`&l9R-vw(FDZ6^AoA9w+>dExhiu_U5(4*>b^q48P=T3(ZW^ z;A{*(GWlt1*2bNMioVAmoBzw7y0P2v+r%f6&reM1Da>Ay`sdq`^k_5Bmn_?MF8{Ri z!a1`#W_NqPSoup+R%-EZvhK0y;%YF|uG2|9@o>Fe)|xr}LIrV39r2ZO)Qd&=ul+is zp>(>jDoD3^!mGWzg2D`K52bOi+Sks^aacWTMuQBcCvIQ3?CN?SnYoMeJPzg# zNe`R6>yrtO_?+ce4^A%ksO`UV;fm&R!EA>g>L(7dE2i(e?#Jws!@T9mchMi%h$Oq2d1_G$gWsZ36bS6cSnDA^|e-7=hQT1Tab*{w+) zHznV_3RAyWB>HW2=9$T};x6rQbx3!sGP!!xMdDwhT*{HflS58Soot=VBEQhxT7D9@ z-p8p%9f9XBnn=g)_uMhVrtQ|{b&o2R&HL;wv#4_4`y(fmGO}|j%0IpC4ss3et)H-f zCFe>_pqxlZjg~C)8S(m^g{P~{9-6JmGz)c6`o7{%{^fZb{0Ka)Kzi%(7^|Nf##t)=i9nYJbzfP!+q&) z$Lk4a!nTw|3r{&b_3*LD%k@3njJl?8=h%NJD=R|A#$jqL@6l^)iS>4~PBjaaTC+Yg zkh6LCXXUxX_87e{Uiwp)25or5b2j+sU)9t1oY_N*wWsWnJH4V(>$%qsFPnFFt=1H$SH8=wcn@^m&zSLzsmmmg>Er|Ex;Ou;8{eCXO_y~Di+s*- z=d8nT6Wwo>9CDUD8A%Pdv-wMZ1Z}SsQ+DRrv9q z2{YpJB}80xwAW8R$GNx2OYp1YLC%c&?j;ObYwxMu*DRgL?%}&Q$6rV;us89UJHMIN zP368R`a*4oxqB9S-4ads9kkP(%U|2c{pI0z_9E|&FP`pTl*V%S){3qxJVjrGRMXd| zYjf=N6+6E>yny>I-ENOFMs*ER=6|eQ>CD@s)h@_KlO@<~H5i zv*OlM@B5FJ_8MqQNV=PvEj+Pl-cn)9507?;>npyEd7)ic^=?bmWbfJ8&y^V-wAc5p zy;8SUJVZ|IQ-9JCYiqlL=08F@c@tjwwY}=B-FfT@uXb-tws@Xw+23MAr;xqYpCZE} zwmkRbIV0#ebydX&*4a5?E4PW}XdU!vKkh01UF?|1tP9_FvQ(_<4v1?CU&~ymVJ4Nk zg6rl6pLxlLqm-|?R0?g~cwkMmt>lXwk3Xk7E!u8etyWtRT(a=O^2+VkcP1?{d1$@4 zDtA@oA|=xb<^ste!6_FM7TRw9WOk0LUE`i$hP8*I zvY7p4hP8FG%+^?b$bPypuxc;QHIx0$7kC2tLRU|D9$0E5R`z(7-(QZ2X5liEEtn1} zu5|vU*UV}e8_|A>yXE)0?YHMR>AiBDIyG^>chhnGT(RxV_7$NTw-4&>J6pAE=~?#E zHHB*=!rL_Ll=fd@KK5zW0+nQ8u4BDZU)}njYwgjjK1p0L@AfsVhT1;HhiotRy^D*e zFls-w{^Yuf==6z8=4q}>@(7BzxZ2xa9h3b(Wd8Ca(y2cze1`g_X>Vn*ywU`X;^Vi`QGXf%9|GrMa4_DS{V@|E{5ep~-R!#4hZJ*-Y!pxzcb z)8+L1zJC**EAF(Pd*jB-`%9nRiQXV}TBL1vhVs_D?J?5@4=?DiV>!8A&$v9n?Ro5$RH@7F zwp)IF`EdHhaKWfKk8-VA51-*b`ntY5=Y5wQ_lL5*=ie=S@M<^DyijrP_x;P4UsZ~0 zS@iwtCadXJ?^~Fc+*vp0k=p;zx4&=OpH}Ye(b~;-C$aIQa7Jjry*=yiKmEHcnOSIk z&cE3Y^=JA%i8wOZxb4dhHJzee%z;h2k8Lbs2{N4+Y>ReqHv_jYOy)cPL*TLV zPL}0eulA-2{JgznUgM=p&mVegwK+8;&D=A?qmZYB(aLDzqT7X$C#58({bT(f%;#F{ ze`tD9l=ds_PaHDOHdsp?d4DiqwT1GGU44^8ZrRHm_O42+iZN^QobZQ5Z-I4={q*>o z;yP#c2B|taHeB8)uPb)rcy@E}qK9>6X3`J;O9$z%yfRfkz3A36Hp!&A7IU`wlh$+u zmrh$!^WhH5hyFUNzYk>f-@gB;dS#9EO8GkXBj3NY#KynzP7aOsag$}-UohiZul{n! zjekEbF}~^j>fL)Do7BzUE;kxm)Na2KTo<(3yn2?qRr=yqtHLEG6i-i)?U8bbVz1uR z^H!ro;MVrXYddql*pza}T2GnR^hiYdH0Rl4J=<3HsjN}YtS@QK0v3Ij_p8?)|F>Y-8p$K?1Wr!LSeyQo)jmG5E?zSv z=a!vkTTAoH<5M@xIPyw77t?Gckpzhme4^#zP47&db#-<1CHKY zu}r<4)m9~FUC6Bo_rwj|9=bSrmrL88ezC>A>sa)q3wFOJE_`Ln_2!NJ93NR$W{bJo z&n;QECBgXP`eq*kmN_a;FSN{>9FJdm%d}`I$K9HvYj;d(Ib&nXD!aVErIcZvq*KVm zXx|NTZVXfS%6c|sc3s*pesSxRe}ZS_kMbP2F7|1;+Lk%~D#bD$j?36QMAuGP=hvva z=G&CFrOiGXzBBgkJ}mgWcTa}&(Wq*FjWg|^X3jFxY2`n>$inLBj#5SA>nGA<(!AGe zOqs2_!Ty-jSBEuDb52F&y`C}i;#>24W3MY^v08^#f2f({Bt5Cy-rF|iVZX+@2d503 z79}k*s=7W|<@Fy6;m69)*Dkrg_1LV>K`!;K`yTo_33F-O4?ka@U0rm(-J~zQaicPm zAYW71j;@H#8}TuVLcWI|VcnAd_3x88FE%pwbL^G4{j1^Z_Olo7eYzGL^N+5GjMiO?sr-Ah%H`j@X)W52C`rO@c^LY>;8p6|*l_o%O6ee~$e-N+3sb{XLj$CBrjy4rrV7YlZ4 znPye#AIIgGDXzHLvuk@5i_P{`{0h7795UI|Wck(f<_xx!>y<~gX{-sKoUq8~fgI14 zDH1mpt`oj4xqNAvl+NX7{+ZknCzpQS*L3L=qtD_M5!cQ%K3MmA*|d+6jpq(lnN9zz zIPKn#b%(EcPM3~RXFl$}n)#^w`mX%cJ^vhg4%j*AZ@1%|!*N^J^*NfK z8-D4S?9>X*L+?L*aSjv@)&F@U*xPqXnC1ui5m&ub9cKYt8-iMxWsI zdxY{IvTWFLNOm$)$BW6OX?6W`3R;C;H&k?Y#X4MPd9zV=_PH&+d#fr}vFz}*f4HP_ zs^Ydq0*bGWJyc@({3F)*#r$;c9^Rq*Cw9@v8EsLiGv9LdhcJ3~ zhRs{I;C#Y{bT`TSZB5I)6(=0N@13c-<#$-X6^(XPFBZN%_gK{5y~`C|7${IBEfEi@ z&@wKaJnLKNW|JqY*>_mOKK7V@Scdk*dA;8}6?ko0dD@Q~2~Rb2c{5p8rY-3Bg^2F1PW>!fw<=U+%v_qp=z44h}UAm>lv^;Q+{B`+&Br@hOW6uJME z&WgYJ$DHL4r*IrTu;Kiugk7de0G-Pac~{Wlxa`4bx)To`hanfJFcO!3(e zQKyx{aZ)Wx>K5OYup=s;Pnm{QPU1U@gO2g|_vgCqEgT3i_bvYsu(%;d7M4yUy3PYDF7X-ZnlSA^jn3lc?2Y z-NkDcYCI9&T4r4E_}W^-EuPI!PbaLXN|LjhHhcFXPnGYLPo8^U_ql6uf5ya#xh?{x z$Lz%wrRK35cK+l1jQ8>3bLFqalwK5G9APc`crds z;T*;#r#ch^T;({|?P{+Jthicn>0iUa#J%5lzR}+Qa(CyO#O9-AGj3GOyz6mgW}|Xf z9kUmgMc+TRXNopo?%hu8vx^J3JYD-o{H-N9`RAT%WgpM-h`je-B!icG%6>NA*7&&D zp~q%-yXEU;vi(}N)#k=cg^mSMa@$jkigq)+JUJ&JZ(8yNyHL(gcb*o_;Av=3TWvQf zrdm5^-;qV}Y~L&neUmF?!ut1%@U2zbtJ_Wq%+C0= zF)H@xQLD+5Cd|C`X#R(&PycM*HPuIMkvVxFMRdn^_0^kmzGThjTO?#$vCuM*Kd!oc z>*I`B^95QPxg65+7)zh}9X!eR*ht1%tBFyy{GHoM-}rO#)q+cpL@7%CTYSvkT<@Ir zpG7W9M3)Li&3K?UcR9Ds!}K|OY>o7luZ13&e3-e5@puu>l3$Lu=7yDMGyRu19-s9; zLBuyO=)2Di>13S^4vXTR%=a@cIA)R~&AiQFuG^!_1xIC{o=X4Lth80Ybw&8iZLd1I z9PUop6~j`xN50oc-BsH5YJ=9tr)N0Mo7q1#?VEdD&OmaPL*iM6g1a@2MQIt0mFJ${ z{SbdmSe0?&ci-Gvj#+nim%L?Xd{(;RKv3)TpRqDQzVTP;3bt%?TK-OE+3h{@jnjT* zX}_2_lc#rYNbKu#k3TLxf4Kbp`Kres?lnyJ+`FUL@Ryq8?XG{u_OU`vUl(?_@)vHc z^Sm9=y)`Lct2EuWbf0zFy9!Q;yEE=Ty2fVZVfZojui1RnsgKJRC~NCTZo4a|!!C7g z0cYfcWeHyU|EqFMxuv{6xe&ZJvs`Dt{TZ9hc9Z8%KYRLt+`iPSZ#sekyYn~$GFk8YT{r9`IvzNM=#y(O@ zdCSXxP-&T2T{uyD`)om1PuPs;uLm_>y<59A$tI-KDMGpOZ}gsW z4KKN(NB3@=6fZISqBrf5nCY3QMR|(542ygxEY^RX>m$Ixa^uwW)6qTZ`{RCyZ(Y}` zFi}_DK2xal$i48yrkPvzbN;*j&M)%!+65Cb6`Ye;f6JX|T+6`! zs_9Sep6_SO|C+{3x+5wUz2eBx0!@|(HhwSvtiOp)pWkvWP)OHv{v|1KzINBiIk8`l zpJbS8x#UjDR4Xfsl6iWyX&&?1r!@tsm)t&L_WY~9Qp##}{i<#0=~D%MTyekmQ2SPW z*r8w^>$WhLhINeF?>ggpMqNV^*Yw=iT&Q&&mH=A=cO7) z(v8Yu=IqT+FMOT*vm&CI+Z7rGD6u zYQx%fK`{G~;_=Vl^0ItOSihQnbw0dy%l4H^v*z4&4mtMl$BXyJVmJGR9%XKsobgRb z*?CFQV(A&q2Q?ag1n2CC%S?6Kvg$(8^S&3B%o}Yc^1Um#v9*P_#bkAFdT@rGw8q~3 zm)PBSf}mNGow@S#xC3rAJ@PB zB{olM=|!2qliid4EWQ7tDmzEh-9PbU{e`RH%XO^(Deb*H$M^H?>oS|v?!`^&da!-X zfsXs>=MUdnR3~@I@JF>v!v~q1s}ZhC-dbf$b=bbaa?+-4FXQTWUExe{br;)e>DlZx z{b9y+-+)*BHf(3_m0T&ll`(Bg>_VwYYaZ%F?|c7!lg+g=F>n9w{Tgs@o{zrnUy;}g z&LM_pPn+#+pZ##j9*2mVx*zm?EOXksy+2RvnDD6N;nEWkCso&HI{)!xQu^-x;lIZ9 zjjN{Ac7N$Rvc1qeSnBnLr<_6R>++v(ewV(XXi@2-pWL%XYR)Q+ zuPI!d>9xi22I;0J-?U%(@yA(g*4gk>ztSDAnA3Z|yeaXXdzvREF6inSjn*8EZLSU3 zXFl00Z8Kjp^-bEFESG4W%e}u3+uq|Ya&Ouy=#=T|<}cLGx%jF{if+7ns+DN6@zXa< zxxeRZ%kFXhGJ~}sfy>q5*ER7+4_PX{zYcR)cgCjJ=oo8NDK=IKqD zO`%ivr8rJW(R#peU(qOgf%lq47R(xJ(~s~>J5(SipPjTjYQl!_*UBo(gH6ldu9=s# zdHTf}H6In$9CwRd%-OJQ_NMNa>tgo#-ZDKpy=KynO1baFWx-6A3A+}pF_&BShy9bW z_YC{rJ^J~(Gzu50D?UAAZk(1e@hM|{!TZ@)Hw6kL?@-&UsvuMyJM(a*WP(h|JkNNO zGfZ;=GxRq2rC9Nv=sDfpxyLCvq-S4)0jzV z&l0sc(`S8pJ!i_CP|k{OeJQgeHoV8R1GImCR~Nstk9*_Z4*AH#8;=+)cl`O$Y5jtQ z6;-FT3(Pr}A8I%<<=n!KsQq(3E_tf&;=nvZ?D~&9&Et_bm$bhWY{}MJ=z4O+2eCrV z`zx5evn7=8?pwd`&E_Q2^|u#y1Oy(rykPBu-!olI+ao6nyuNL&e`f#114*Bgo@lWK zocZqZm&fcZ$uxp~g!@wC=E7 z--puXX8E?9zje`7&b|*1MkS~Ib9B!TsrM~8;H~W1xmhB6!iMI(QW^WcWt;Okzs;;< z-*fdv<`Mngr>qtsk31STuDu%bPk!yTgR}PRuSw@wJ@?Dgpt(x~+Hh7tAZZ+D;!_GF+_Q1YTBi=9IGRx6aJeVGwZphys%u#?hF4PmU!2-s*mM0j$CTr zm)n0Zu<_S~X)mkhFQ~h%RPmPiv!b%m+n*LO(M_lRm&z_&>Ui7hX{OZFL#(NH{yvHm zzp`}6;j>rx-mo3@m^3-$T|nOTOjc`&YMD)f3;TD9ADB3K8D}QT&ACT&i$mO00@em^ zT@^FA{*ut;uv z2Gdl+>MkT~_&1wthiC_b$6v>ib9Md&Nw=?_no(HvKjFQ=mAkbs=R3brHkB>?$9<-@f7!g6*X4SIr+S`A;SA28Gp$@7rHt0T-&xtc zxM2P+`_fP5nVT{n>l)t7{jfKnOgiGZ01d3G8@_Wj+Z7&u%xlgU7y{n(7tKi z?C6W9+iE=GUnbA2?>|s6J!qGN=}wNIeQOr7alhHAb*xr>%RP~n8;NsHmM!Q%GW~~} zeZ_t5vWvAB{#5eToHl#TQ|!58$HCnntY)s0+h22ZPV|B25dxL&2kh7-7v9-XIOl%U zg!OgLSDvlVuT#=pown)LwY8_W*`(UrzMZ(LYndAN=gA@8{+X_1wv%0*#=Ifz=@|p{ zb+PeP-JRF`)u$cm+$TG8vB=XUkGvayYYShfy|yZ+`s7}-!UL`gCrNEu{4poTtlfd3 z=j^1jB9goJRd3NMX^}pe@$zLPzws%rH~+c09N0FMZLojxrv1x%_Ua|Jrn?dz_pkn9 z-)QikP4TDt*VTOtjqUF?SC|#4z3wjf6POt+S5>BFcU;{=#$wVop?MbRg)h0i%Jkne zaBkB(dh)3L1h2P0pVa05z5DvDj&a`1gRT?(ctg9x*;xJUVkGY^Hv1xZ*v&FrsWfmGb(^a# zoFQ|??f5Rn*o(FH{JSpRn6_l=8_Nefp6_Up>ff?L?~Z<{l+F8=N%j-O9TI-IUkFzy z{xI`E^E~}zremTC2lt$i3$C*etCu;<-y8BcVza8r8iyTsHd-z$w7nR5?bAD> z`rUZ?F2nwKC)=;bi>y?RS#4b7u}N#`-E47=7YrT^o^~1hv+n5nS#~^{vbj_EXXrYW zINfK0>ZT{VOSTvv-G5Nj>3{O1)$1lyYh9}R(BW;*a7%yJXFbm4oxwpZfAxfP*5;qN z<)hOUx5lWHu}iqw?lKp*escBp4{==oet$eX`_@Bq=O>&tdCIC4)h~L!mqk2QnfS$O zpWCN5yUy+kTsdiPlwNlKnRpfbYY!WDMBCp;Qjimw4nPaK`61(rj*Hdne2}NLvEj+Ol+{hT^@6+D{94ycnQG`C)u(;MTSj&^ug{jNlen2a zPdtB;nQMMAk5aPv-PxCpFRkgl`Yhhahj*I!$J0gvhpsqYSDq`L8lboR!`_BfajhS} zz1vgPJmWa;+*iwN0`LDfdV7uWSGnY|PUq4Si&(tZuwArU7iVrS^x;+JZGPLW{BxB> z<`(S>y=cN!`23mMarM35>~r#d6`h;alKbJ{ly7tOjlMQ)kb9aQYbNvhWZPCVc{VAz zsb7WpJD*$VKh&Mv&(>m2RJ19lAB=$))@TDSa8sb30zovzY1qG5L*+mSFV7wXU75?wnFP zosMz+5fGkl@0q<%EA_u_i8W(mtZc=P6#}7`CEiS2lOxV((&wVL?o77AMt{9Icbx=h zEszXjhz!3ilX&I+>nfLX$}bB(3H%FOzdSi|k>yb~=B*R!WkMxxY+&SxJ(yaue6?jU zlPSBr`t$gM6>B(#8Lx^-M&R=Nn#4d;M5sdVuZ4IvrmBwSn=R zo7pWD`8>{xDK;EGeD~~$i+k3~ZtT5~(Aw^kQ1s_Bdr*$w&Wed8bEnC=E0$!*s76Fw z>G?WuSz2>lPvShr?{@jJ>vhj{ zgFmX=Z{)kbZ_(+$PpbCGXZb~Z=>781>BMhywFh0#HfJt+oqOwkqM4}eJiib5wvtKf zHs}}HRLH&E-t*N^H00s^+QqvTo=?zWop|?j)XbAbA6VC2Snc%K)_>JZA4|p)-2a?& zKF{cvuv)z=plU^c@-L@%djpO*iM>iM>9(B_Y`E;H+xc~$*-H+Vo&KiQFz@58C6hP5 zFBg@M({0G9nJCPlD&&{?%Ua8Jolwl}R}TUw%;<6`xU(ZvV!BuBvkS6;KGN1w>r6g< zRp#|rC;H&tnWWK!}x<{eOy zdfUXB+~@n)TTe4*r&nDJmyeBC!VI2A|0N606vcW(u#201FmK)+E6h;J@<@BbOt*(O zN+iN^{&6n3e5hyVwXT?b(RJKfJ+s%9JnQ3lHQ{20*WY(?-IHFuonN$>A;+?+*XV8a zpU_h`R_r_Fyi_uW?O&bDmlXeX-|AJJ1cWxe>33jYVA^QB;8Dl=?d2+kt0ysEHG87P zuef91^upGj&Biadyc`}juD4-oVpyzjzeMNs;*^j15BA zyC}U3Y(A}*yi1Pvg2Z?0Bj+ASI-l)!cy?vsH*;B6$9((|-l?9@a%)G<&-0f=bfwn#GLi6F=r~e=;h&Jm2^AQ~Uj^ zI<}`2O=X+9{BYxpaIp;x>K}`1pY4h)ekc2GSIG{c`F9%vv;^S(ZrgO~b^7C&Q{{>D8oM3pf^svr0wye;LzBSRb6KXccf zesUy}`M}Np)l(m;UkI5(R(!+)>>%~0Y$Diw}S8gUT<6F>KwA8(xTw8h(aCD_h9LoXBroh7w>%i+rKfF zzi*Lm;#TInuFVY1Wi!^#5Se=DkoU)c_pjypqPKs2FaA;{{`BGxsT%2XV$U1zc+cE^ z`@pu9R#%_L8TPMSA?#RoF)ia|AwLJ}_9Xw6saX<_zeLv_`F$ZX;KIMyUzd{m{ybnj zUe&9cuAsT%;tGyER%O#mH#sg^-|_gG$mR1^EX&Mpo6WynJA?a{*uu+?1Ne`vt}51i zsO>o8=Wh9|tj?WxFMs$@yCQvlul8HjTW&X=ZMdpkvwHcnU3scMV?|v1&Hnvzvplq# zSo09zB)_1LY2QOB-7W7M568XOT+`Mu*Y<^0PF;xsPvvG_sefxK zgXgpwXnw!`d%?TSd!FuFBO4rfLf!V^2IDlz6eB+20uxRZq34%A=HAs4GPg31^|9ad zx7PBe!#=iI)<2t0H9cdUvf=phIX7DL=D&Kob*=pZmh3}{t!7V8h))56e z*7M`8&9jgF;R1^;@)orG6Ta`O{B!-k%8l`^r*9m4bv1lm?(MU!AH?t5tUs)xV;(Oy zSA5Q{xb$gPa*M9syT>puv+-Kb`daQI%g-rFJ}dNYELL_8I`gGQO?IUn*PpIse*4}l zeP8x9tYQ0IW!V=SF9y^oo=B7`*kf_)zt;&V3-%SSgWA4jyPv3G7J3>X{aLs7;ZN12 zf9`NPKTix-zH|AVdXN95xe@&u>Jkn0m--KHD*R_I&-Luw_bbd%s`FeNZmKdCZacG; ztMF~5(BpiAnk~iFn}T-TYK&>S$am`C{@-QGrOXbcXRl5AIc4_ZM+y?r?{C-d*w)UO zdELO8xzl&^YhIhkvsp@;l0?=Wk6a;A)W95FxAsWKX5%Y-D=s|Q_icOY)XTC;zkh8N zm5}a=l&P7^KjkO*{3wyYBCnZ}Hh?DjK$3_Na6B>GD>ABPk4N zZuQQ&dEeBy<%&w|CCiQ;Y%!m+T=S%jvB8UPuOs!2&$;eubgB1Z?4|X0S|#L6O>4qe zd@B@*`et!*H&gjyb~q z{X<5LTiT)a-ACtqcj-RzS74`);@XqG+tl^#Jzu(sYBC!5B`W?q;GUDGJ}=(cm~qOA zXGc`M)>Z$#WYW9qO-tw!7B5TDKVr>Z6<3V*xydisD zn|_Klo=K9Ns&rBRl3~`~l9S8<2Sfjw$NvsqGylzrcjCWtUaU>abt{>Dx8!*J`NOv^ z)yZ8b{88 zd2>fk)8~m7@6Gp9*r&>HvHR!>XMKe&Kd<+jT2B*6{xG?ysM_)x-|Wp1AXOx!F=I*_Tl#YcZ>8Kj`BL$usiJB+$?#iV5iE(8yQW$(esKd%_ev+jTG6e`9YAU zt>W(dApMk_GczYD_3XaHFkNcvs`pzoZEI$^y;AN9xhbr16f5=DOZKjdh?kDcOHwMO|u`GzY3jqV~39HySoTO_#Z@TwUm*^XM8 zyu2Sa?Y8AvzwnYE-?fSL0a`}|KP^^EGyK@Gb@ImcT}JKSc`RME>5a`_y~X@FL)C9St`%L%XM1kM9jlyt zKWgL2I1_2-JdM`yvTqkMaxO(`KiYL6yrGawki#QBjPZW^=4m{UWt%VFYVy?0bo{*Y zK=$*+93o2+xSnR_CRwg`oL}kwG^)XB^3{qZavzg)??sjhiN9I0lQV4wEBmC7oU3(` z%kpf3|9J;ax*yEGxZNXskxhy}*$F=!JP%FXtW@#4W6MGtXXn57o7U#~W-U1= zBKS({c9It7wq`D`otx zMqj0wt5Ys&a^k|3H-w~PmOc^Nv%oxkapHwot>AI+HP+3)$ZokpVK0Fw4;x#aEdw{ zE-jgR{8+)imFIV^+ctMm_E=krhF%4))+T=MeZ{^?cS|11{VFLq=r z=;{eA>J>IoV4p{QKXf^3$ETUY(nIO6aff+4(8` zF~Lphc@r$uOcifUwJj-$W&h|g^^_O;*}A^}Ok4)1~TJmy)G=cH)O$v$$SS^e@MCc%m2$n#{|O(&-Z)I7rI}& zBEwGetkA3zK}WBq%q&Q`V{^ROy4LG*)=VBbh0BsZ<3m%`Zd`SDdwDVMxt_-MWVYLS zEK&zs_e(9*pM5)TK0})x7KbybC|u{^iWNmAUshzs)}AzvSl$kJxFV!4B1GtHV}Z zz4&oOaNe!XL)Vv{I`B()A;-kr?}Rx%CFVzm|B6 z*=?EHA+;f6}Gr z=j7$8oT<~dKAya0lH%tYk)!Fm4vS1WxYBO#33t1bj4fOXnxC@py*$)(aqq1Z^K~M5 z$&HbFw{$lDeeaoU()lv2rTbU^#cbaNO6;sRmCr7${k-*0X-Hmm+bx92Bg<;cO z-t+8Vqiu4}%h)Y3`S>~pmB*RB^*y&Q9+!1n?Ek)LO#pA0pXHB)Ym44a>-pL-aea`_ z#|I|$((`mIvpM|*iiB*h7^-#Mkm_0>?)Rl=PVraEM|PX67_YJ$?C&|Wq)h+zxnq0} zPq7}|WvaFR*v1X3=PG}EJnK$G(B*|X8)ZJ+7m=0uvDMV|!CAK6liSuEsViLi>aD=M z4vu4j8ds-CX?s1_+*){1Yt7U=SNG?Za}KDORNcxG4)Q(q@IyzQN}{?%;SvFXXWNpL z>eqef5f=N&v~ts>bf)vUsimtYD@k%`|I1apeC6hw_ETHDDkg{Cx!G>%=ig`On{-Nh zS8aFDtn;g`-tL>GUc?#jP;qbg?ghnZTedF?tKC(T(y_AF=G8+(sS8S*-)Lr>dtrOh zMcQQ5ZN^jG+g?nPaOliBWOiG6$8Pr7dOzh3{hArpGe5i~Np z)b?&k@qe+^i+1KMRBwC|o@7|4&(J2jY@@k zPVSw1`J1>pnHF0trEhl74G@VzMA)r%CUn<7yDL+d^B2km;J%ksVYHd&*$7rFgtAObKz?_ z=T1|ZRrQZL-7}_4bF;DT;Mi?*qr89Syq(XapT7w>TZKK(b{0l<6B;<4#wVj*f zYqem)p^XxGDuuPnwu>g7Q7c!(b-k!E} zmYjBB>8)e$L{gt{@7&^a?$#H%0OP-c`^qQCgmHa;VBfaql2FS`O$~-+-y8PY0bj4zcn3i5@EZ)-uA`SPkAnvzQl^?aIie8 z`@8;-WAfjs9j^{QE3UOy-uUBdc5Z@b@A)I``@*8W+)cWWWbD;;se6@tFl&*F97m$3 zWk2^?<(F>-dPWtNnP%6i(gwW{sQ0&A7jC zPh7}dxMB15{ZiG^wTBzN8lO6SZ=c&1p1r|rvy}SpuDHDD+;^9@9h+yqQ{S@OFYCty zPtStR+!uMLE+};@OKuAjx}d$pJ5{E8_KiTF6Rna@Pt0Fw5Y{T7^8BZvLVwnxryX0$ zn?6cCQb(}qBk?zE&eqw&9CIzQ`r~g^NX-e^ zC!3%7@#LFn?+QUavOBo9mB6YkBPn<<;!lyDs|EPNQq8 zyYHlJXTGJfCI8&Ca$8>B+nbbUopP<52fUx3e<;^>XrbTn-G#TOJ(&7DSEM;wzUlYrj`xaR(hMb9Z>^~^_O1^$C=DmFHbn43Do zmD!zLbVIB3vQzc^T1)h{NKgKj&#TC?)q1t$VGDO91u;w4>Q}D8^(!>C-3-2Hkek@J zGf9)TV#U0WoNF`mxLyiq@ZVZd=`p>kJx4BlU+<@kqoGR+ z-#yb=w*J3w$Nq2ff{*5@o!M_@P}9osagIfT-G_a@9(*98mQoY_Gsy>V7C?W zGR(YwdXs!C7f#Mm$(e23$htq~v|QLX5i^ngmS+q5SEa9H{66(yY#X0?<2#!p471yk zeq0N#JCfx1b-Lrpw^G-djuc!I+_iOs-EaTvGj!+N4E9~8plq9Z|LV8-8+NVkmwt99 z+&}54bL8X4TY|QR@@3ZTFMDuzAOClOw-^u0 z3+HQ{+D%HQj%>U3R)(RCIcMXET?}^&zyChKo3!qmisF$8Yjo|+CRJ~qeAqz1{+fC5 zt4;6Tb7m$6Mt3Z;nRSs}HgnQ{&DjPU1P)6z6>=^VSGoN=_|C%{S--9~gatfXB0N!- zt48jl;IV%~4|Wz9h<=`vP;P%a_@{;9lF;Jy&-!9J|5^@}x<6oss%%?iRV?s0CBDD7;MV^1txyEW32A_eC4Gyw}H+HiSHGxU@z5%Iw*H z4%&Gt(ccxh>`U6aKBRyn4>IOPg$?XPgP+_w|lz*mOXstkbddrkC={ zg;N%{u|A26JZ`-&!F}6m`4iT66%Kw|=DPBhfYx@ly^1rr+(cL3@cwa8tNDQLnyPOP z>LxNS+1I7;^4#<5Yo?!2W_Vlae>t(z>e-pczS8+=^UUq0Hq>1_QnAFnWcNYUe<}v2 zrdQQ%t>q+zYde5s_%IM!;zUYmb|NjgV#gs+9vr?p&S?;>E zm9L=FCO9f5_*7;=UdY{yz&9mXuLT{lwJt>+3_DZpYs~astO?Qqd<-WGdo0+WOvZhQs^2=|w7g9U(bORfI zPIyrGIHKuwU2AManUmw&Fe~3u$$I5U*V_H38A+ud-@Gf=Ro;Bc<;SvZHc|goWPR7G zHM|y@CnI-h?e*6ltxroIS=DULKC{~S^asQF|0~+(3jYvjzgJe4ci7u1b{^kS?+dfH z=vZ_%v-?gx`DxX)L)NwH)Ngz|nRG;GR(PoGQE&HiAKz?P{or?KPW4P?bG|msJ?n&J z_jOsvEzmkRaf037T?H#X{NS!(>3$l?VK2EWP;S}7_J55cej)7%(%)BXIhOIn{FADd zWcyk%uJDJt7CkfiedNBKFY-1}VLB7pbU#>wD{5D3pjw`=eBPI-0o8LNsu$c%KC(M3 zwrhT4x`6vTGf(yt>u=3+Y5wx9E{`Zu%cdmmLX`Bt0m|6Dd}i)`=Gmn$DxE%_2# zJTcx?I)LGsZi#5e^IfK!tT*OG%(A{?{(4FD-`lEF&PANs`ts~n+uiedcdE2+d2%9N z-ZDhsKEv(rx=mAEjE)?O_3`u&d6>n!WYI6vBRf9(XkY#pkel;bNH~&DFhxoFPkMU4 z7!$iiyWA}Ah$H!BXO8{Lm^b^*lxOSxdc&F5T==lDebb@5*4J?>r);0$D|o_L_c(=yxKOFLx>Eo4;_J&(MCrYRvxg>A!D8$Zq zQtFkXD|L8Y96QMNaBjxL*-E_A7hc&RKT(!3VBX-X9mfca$nn%#>?r*ILDCnm{jKD z(;JUIn3Vo(>WBT{YREE7i{B7y?=lMx{n|u4uOrQ4j)l${GjS_7h zsc%o;KY#e9SDoB(!ynar4Ih`OMn|#SX#CF*eg4PX&}gmv2fI&{&-runqU+sW0mf6( z(uz+Qy$mY9u1yG;dd=W3d%{G!8Nz!%G59~Uyz1_d%a!v^FyU`fM)9#O?>N?;a*?;% z*OxFAt9sAklU~cby87f*-DwLQcz<=*vaUbh%y7VesZQB;0na5Lm6qiNF-}nQdTvyw zlgc#p?+uX$zi&-oyqT80sQKr4p6e0kdjgh*1?POsPCAtmTITaW_Wbh>!3Q7b&8xY5 zk8hDsyZV6Akp<4%XBRb@1nFFH<}Rz;l^&RWdhwG*yS;XCGx{ICB69zp znEn18ZT-)}Snw@#4auV0UkJ?q%`)|aUKe$l1x!ADN z$?fIkp7T@A*%wS{n!iTX;=rkpoD~_-6PKR8Symtrpq29NV|MvmHy67~_sy;D74s%d z`|Z@VFmzrt@8ji0?70tF7sVxJd9U>4F?ed&%~vb@X7-uGb}d^s3kJGR`6e9F^WA09 zy_eIP@0`3MxzFakmZH4;>Pw~(6Uvu%_bjsu<7`{7s^Bo!iv1Tf_u9P_-v-Zq? z=T!ckUjKC^y7m9+6_j7Ud8VpkV>NG{)Hc@K0uv`=k)pqMiUYQ-ybvP5zLoo}LhRks zkCsnQcV4Pdct^JJ2H)4-<76u)%k~3<`*on6rXOLnFxs*NV;+mMb z-^7BtUaB`v7e3Ls;oRMYN8S|s-+FT5_C%qGE#2SFPC9*N-Gi%}+V;HTUUjWSx>F8ExxE!eTFbA{`^6$6M{ci74Lkbly2!B|9O9RK ze?EDu=x@)ie7NG{vA8R0_l_{0nsAV#JTc(Q%|!+t=J)x0RFlQIZqK}Ban}EHw>_iG zL~FU9alfxrnLo%*%!y;q{kAu7{Vua?mQ7ZqO_~~M^>a_b(S*$|8jW+h|NVHutPmt| z(5i0vwF_Kjtub=D)ns=j9Jo=K_4cR7`GR-rr}yu^p+0rrn|U%ptLCfZ9#MJg8f(a* zwS8@DpnU7p)bxf=_d2g1n3rWK#3}JB?&7ocku6f*bHijVr@d}IDQ?afac=&dV;m-{ zvnQ{-m9g>RpOmr1Q^`zveOLmT)m^I3C!2_gISk7GsOoH(c^Um1oAq ztIf;(%(q+ZL$vCmoS*F*!xwxLQ)Yj=xpuQ-+HAXn)w!#Rl;>WX?PQa(N4u%y(sCZ5 z3y+_I%h8m1Cdn^P%L_UfCI4jp=7QC)@5j_EO!&C-=rT zgT3YbFYM25etWR@uZgkPa&6ZD_9Z`$ntr4! z=}0v9uG#jdS6AGeb?438W`hYCyw%ZAbW3ys`AU1LJ&;Q-V^; zZ>GD-`ae;5k#M|l@utx5FRYVRIM?oFxD?|cWHV#pbej##q6`-%pW>P%uKY38am7yW z)qi;HF6h;_+j_S1mh*ks9hWY>#ioN(Bt4lDIZx%-IoEw=U4;Lx#oM1B&x=BQ>lHmae^Nxg+=Q z>HSw{RqGp1U67z`G~)8KRDLQ^SMelM`0ZVXeY0D9Bg@zB zt9s`Y(=WdKt4Z;$`>tZ&pTutpzEd{M(^cKWf8Cs~=JUeT-WOHY5F4d2pz2TSjga?0WelM9E*`u-INkj<4 zqGp~aJkIrTg_SJ2zD|Ot_to9y&5%(({c+#L#OKf4=SRQtOc0y7&n475HHu9~qKUgI z>*0|v1^j!aa_foOWlr*XvHi=jiT@ou6z|SgI2~0k8}zcf#c$TD^D2(@DF@QJSGTW- zleRnjgzb+-Wt5HH7eg(TzH=XLs_ay3mSK7Ea{7&LdzU3VN&Q(=TfANNX_>6O)N_?b zKTYz^h>CAcnrryS;oyX8i6z4QC)|}D9TMlddW(1ZWP@A(Gs3#Nl6S9+68;@5u92~3 zx2<}4!M*6!(uFmTf7hjS7C89ozuCLo_lBFD5CqViYZu+z+m`R5}} zHO`bg`8uCBV;*bx&JL;yGL{^$^O%77exn@hT{HRiiv;IK@=W?$SF z{j{fd_~iZh7leG4TXtaaV;<%DZ@*6po0Lhayl&$y`ysL{%I9g?a;{XhDxGJF-*nqw zJ<|Oy7P-tKswjtJ>G7V;b%Oi+S9yu;4V-8<-QxS@ehUWk#y^K#s(RL(_0U^q$rUaZ zW+rX>@wn-1{|c@2`Nl0dRXQg(eNbwXio6+-YQ*${~+wsKZWpq)g$zwd9hJF^8$ITXKXnuhC2ed3LMJM)%;H@=tN5ejN2xxE*uaP>CSCn;p}H;wJKjcb zPd?zdcjh~X(;2S{^4m@x-q2HfpSye6jMk=P{t)j84EHY;7#f&QHj0tFdF%V<8&luN zYKSR%?FjfjN0O~^f9$HR_mZtVo8H72>n*Y};5pfR>7t92g?l-xVws8Z3s1HglMOQW zwmiKRW4Tj+m-TSsagAep`^DxdYgz9h)NEw5W%%GZvpR zxWIf|S?W}jTtU5)vIk8;r;lsdb>UVV?EBohtYq(XstUIG#YtyFS3C@?6 z<#yO#`76>8oB31er~Qud_+L}4=dKgAxlq8Uu4jL0uUzK6#}|{i_AK%KaBbnll($#1 zceOSPrgOhs_=>@jiK{o(YN>|Ik#}>FA{)-#XI^+(o-2LrdmEnTYB2^soNn+glhEnIJEyOk${m&B)|kI((+}mS8x=PDb_O5J`7FC= znG$c{pPfFp*H}N{)ULieK}D`}%B@O~2(Pj>)lI+Mt~@>F=BxO93Db-hH@x4yeju4N zMMBi@N!d)*gIq@I-6F2B^PZIW^z~zEbx_(OH-#BB(iay-p5qskzS?^CPOnvq{M9+K za;HpOB;?<$eRZ>W}W8& zCMJ)663ZViFPy#L>j|$V%e+3ZE8RZ!>)0WUr8}0)W^!Y6ZF88N-M(1stMSQlp?gk8 zXJ`7A)#YEikZpPU)8XuC+wEGG%axX&w%umz_(7w`FUs)V@`femmBAZCGLnl{eY`Sb zciATaPX^0(Ttz!sK7VJJs1i#FBXoLIOpP<@Zv=Gh;j|MS?Wa2WWd zojh~i#Mkg}>id?jpNk(#zc_jI>G@6X1f1XhGKo2UHd^?s(xdSG5sy6eZ{N;xe(HQ8 zMt|{ZSvPeR6QNHxn`UobotZdA`()#<#wF9@cX6{S?fiJ{S%yPirf*Am!nIh=e9MLP zf~Qo>KeQfww&mK7uiEl5wn7{p>mC$nDIZP!xUN?;F3lh?afd+E{I@SzHZFZxb*JlR z{(rr+a8IYZrGz+ zki4|tzm#36YAT!MInQn8WhbqB_uWqa>GFeV&Hl|!nhFeePTo~vYiw-#_;)?qVqN=9 z7d$wNP0!8fW@>Fo-pLTE6W7rCP3N#cdE8Ioq#nkn?~XM7U9YrGwzoFUvd;SA-Fq+A zUstqq^_#hLwcpV-Cxtx@Xxmnn zMy~0+DV1=s@`7|0znTB}B*7P5T8_uwpE=~SbKN_>#{th;v=;82=IX>(`{iT!iZ+Wk z{9Q}f4)Ty8i2ml-SCr6x+@EU*mYc%e$gw z>?fpWu2$F+)7ubNBvR^>v1Zp&+v$-p&AtyVR-O-eGRw(X=FT*EOW{z}uhX5McO?8N zzcTyNyV=)Us{iha@|q_0q_9*dr$C!evcB+}pv>fsBixU&Z%G~4oGZU>?>X!6Z4*8n z*}qmmP_55{bvyI&>I0iUe#~gJHrnStt<>6R(d5;7?9NpUEf>`yZVKs`{ViFvq|0Yc z?u;{5QYP)~Yr-D-f4=fI#P=U}_>~<^TmrXu%(c20!tgP%`0K8MBN_AAPDmd8(){PQ zk$ghkR*t=miv?$zzSAK6ptr&@&E?_Ihv=iIMrcO=7R21(p} z^V_31c*zs%sM#iau5He^)%>yNDd#)m$vbi+=gVmQ?R^-?XvA&Ue5bSf%VnL5yZR?R zKa{1?>f{?+5X)how)&z>jQL^X*dX3Z|CAnNT+j|_5VV>yQE#u9yL?K!v6tpG{?ikj zzsBF6U-kX@eU>`eM+{#%Sv~F*eEGV}a`G$Z=KktVdu%Fx5qI_INFzn`D`+af01h3^TnX7nKig)Maj;95aHk_DvS@}ko^r3B^ zH_Xh?O#N}>@V~B12}XN^mg^j+p@acjdoS0XC&`K^tI=h+B8(w;5cx^hyIj%?c8%3zsRpWb$X;HtPbuHpsbzYW7;m+JuYv)*ss@4A0YIvdDayhe7u4c;C^23HPb3Ejf?$@TP=!O)#FR{51=b;(1m%UcalKX?H z(z4kn)G|$7o8%4#%+V`yzLj!`z4YOA1$L8nUqh`~Y?FLm=xOK&oCw`BNrk2IuuiXe z>6#x;XWMXZO))%OXb_#XCUDQD%F2Ip9^a0Qw~5FMdc*a$sQgXnsST4@_Y|AAPhWrI z-`b~*yeYyut0x-l_vik%cPpR8fR@Yn1|L1@gJ205mzYjP)c9~Llu zFVmcG!^UmzMcr=eqN&=K-_5b=Nx4(Rv?(shO`&R?4zx8mi`H4{=IFPcfSw%ax(wEnlb|NGa=Z&m!;rCzJbI_Ng3 zF|VFq(ScRCHo)w7c6J0-|d*=G5!9x4YRJLznm9%Z?@lgo{OjQ5^^`+H}KKP zTHsT=&CYM?#SdX~6db-kuJvE~LR?tJ|J(U*W{fhj4|5Z>86G+ZE3_^&7Wr~x?+1<0 z6$Q^@AJ6~rj_dK;`#v1&&20=#9~nIGlv%&FjcKCANv)t6f4l0m=Us8!anLi*Kdm>( z;>+gV@6%JutJgSYJFM=|r5-VT$Hb5c=Ku zaq_x6`N|6U{@a|tx13ZkJ7|}ocQalnDniTRaOlp(egTsvEtGo_D5E&>)m3G?ttts$ z-l|*4gc*o^j?vE8#%yl*bFbgOsS6h`XV7p8w!IQ5RK>Rbr~kKi`?g6*T%LZTD#_uE z@AeCVo(CodbMQ^j*~IM0DW!Vt^Wn{9t9|-c@Lm0WZDaMbj)MlRb3E4MvR1}KF8dgx zm{j)Bz1%^(YeNIA3j?sYysY2H;>-I*?$o0SW;e1bkSO9CrPM3Oj`m?O2 z=2f4acP8)r)}LJVeDD70ewF_^DxUH$U%q2q#NWa#t14I7g}m2VU*Q+y6KOin`SpUJ z;{Gs?C(O2HQpd_xy;pkkqe$S~x)*K@C*S9-@>H!q>ZU0w^waKS;p(5&U)fjgK08-J z_06K);@0(dCdM^1PG$dJHOVz=!=0V$xjp|CzT5RZS?K0IwxDhACj5C}xpaER?3o>Z z6obSjHhzvh-H_j+w0>&v&ZQw4pKBRR`nn451Z%j==*p5PkuF@xFS>1Gu=;%VDG6t$ z2HMnbvWmRmbMiL-?@g2T{OgfERB-K)afqf_?)OD$lbbywIyW6&70b!$mnED~_$cH2 zb1zS~!i#Ew3p}42`{!=_Y;$Gd_9jk8!j`ot&GWV$-ofq@=Hhu_9kHM*PofBE7ZbASH6o55X`X*y3+ zcE9P9(*1PTUv^f)+s*7JjvT)Hdb)q*+zZ_=zvX58G;#8nZ?sfObd7ZQ-2F9G+!xq+ zKb%-{OkdNvsnje~j`7f-LP+(z8eweMraQAwAwqB2nQsKiN-^%?sT-H0! zd10t=%Dp_EFOAO9LYom+X^R`C!cdwj9f;;ZJn85XmpdTTKKbkgl+ zT{LsQ^UT|kDjxsie!Gfq$*A;LBo%q|otJ2B!I{nZp?12}XG*R9{15K?Q~cz$^h*2E zGSfegkNsRc`H;h#h)c^(8&A2hw^+YcjZJNXs?CjeZ##Zo+ZgS&W1&Z3aJ9>W)IB_x zUM*yPUhm93`~2E+(MJJ}pLeV4_AG1U+Z#N==J*TggzutfU*1{B9j2p?QyMNI) z-fWGUfac}4eXKRXjQ)oelJyV9)i?>n%G`T()hISG=;x`4sjqy!o|%{|n(Fx`==>k! z%aJ1aE7hX>DpT7Ql|G!auGlF4W6|!vVhLBmzo-2@V3s-QMepk6+pS+|{ujwQ-sB$e zvS{1Ku=43?iPf+59Cdb0wwmcMTaoR;_mxde_e>`LW!qo5@=^c9Bv;#cyc)mO>MfF% zVBW&KTxiGTAn{9~XI;%059vN^SDDI~k(wTIfHTac=>CyEMpw5a${lpmd1JpmRp@d< z%k+iIf2tfY$Z(Q7%i>l3dH&Um=#AaWeLnp!uU|T6Zj;8`O)+E5I8R~qt}ip1Vhx)LqaQAN zayI2=nS#!@_Kji}_g-DU_b(6I?>Ms+#`lUnLN*vKIN2P&y^6DwJITx4`}DjGSxt6T ztDgzQ9lg)^sVGROj`vQ6T-=j$_CGQMWLK;0dVRts^0}yVu;Jc2JS(IloG!T?y<%D# z>#fi@<<-0*#qQvsFTq!`#1o?5=4i|;Om-9QS;=;7rfsA41pce(R~1?hR;O=XeQ0gC zSKig={jKIs-e%uy`Dz0?td>k)V_R2Q&iqBx`&)}kywknUU4OS6&w0=2U)%gUS#qVf z@@xaOmg7$+D42V(Rw~!NExEj({i)F5Rh{LpnD#yq=FvKNvUv5CeP3QI@6Ik( z;PK8YOFPp3xMTZSJ4dm8;oSZTvzG}=Cmk0_Ws|tSpEGCqyFKcAPFD-L`>osZVw-a9 zBkM4pC$q|4xj)VP$m@S5PwwGo>1(?+4{nwDcX#=G=E=+A-%VVwfmi$9&ofgR+*>Z4 zb@^)~;WXQ9_Ph`JTk_KEgV#O_Q_W3yAG>`?$cp^G(hD>sf^6L6zoI}wK1l0}_?~$60~wK8?WYj2i&66$lowo{ z#23gHcX-~}^fT<@U4gs~&322g-40ZF95y}lxcBMP>YJ7y*?yfZhy6Z_#8O9<{q@@# z_1MhTRi5w`ex?3ieI6`1)(#eW%xN zzrNac-$~}5+`;d+Unu^u&8=@=sAu24q5Z=bo_}(CzTbYq^_`V9`@{CO*6*^tzM*Tr zG&I!hk>pw|X1^{t_np>;*~gQdo4;P(#d=A5)fbDbFMIp)qn8Q#*_nx+;1;)Q%3<5M z+Qr}LT~(;4cy6+(>B~hz(Zb(8Hr|)|m{6j~GcR^>&2?e6{c8+gHKwV|sZ&%|8-LP#_I1^3 zuK!xnAyIzMZUzL{M)$PKPvi}hxNh>=LdquN-s*GL8LfW09DcV*u|lZyz3QRm%BQ=& zTsy~flEGzucgqz6b#=Xh2FZt4-<&lmul*jiW$nT_d?!qdGh$ur_p;nNqrY=Q{brsG zdxAV-HD~drb7r*JzgU`3qFBV#v#>d~*?5MM-0Og@8~bkaF1lT&xFU1@rbUnUIp!QI z6I-z(R=Xl%{gS%7tN#~myuVh_;l>M@q*AfpqPINPYk&SPqM6Izq*-~_a&6nDgQn^Jue;)#EQdvHEJ;(#A{1CR=y?jJesi zX!7x_gR0l#j^;jIGM#za4ylTkXl?81n^xYkzkiuyuZq<7cUqkSCcBIF?+Z3f@Rj?s z_T=X8^X1O@78>~SZ|wZg`I@(`*ME8KC$K3L#!c&YMtg}u!S<|>?TTEEvQd&!TLs;phbA4BA3 zy{QOs?_uFGea2+9co~~xWycD+IfvdSB;GGFxoN@`@!wD8Z%?gniSOK`tY2DvOt)WF z-ClMrJ7kyjmDx2mk^6uw^eunhSo(I-*Jq4L{7IAU#?QI-s<~0gtIYYj zl}Oe11E0=Ws+`PNDpcvUB;vF1sax?*CLg`loA^!pz-)JX&q|9W&QjvOCaFBzEZ2Urz3$%C3obAI&^~{QqrPdE z>bu$Br*Nz843SICMF3zcX2s# zweFo-XM{rQe<6{y64jd$mu^&xW*L0o_jG;IvF*UQvuv?%ezSWPT?y>I@Ae?6QSgzO z%7X$2x1GBJ|Jk~{SlheyFVtCQSy zw;o+ln%oqngC`%XYjFeIizn-NN}$_{qn0Ri@o?YH3sm>>3J_zfzKx2 z-hYZt=f75VLZLp!<->up1^$kL>n#3E_|WH|Hc_$O&f?>RO`8)|J$tp<_W0sQMXvu1 z{xZCI%dEIhfXVk$$P*L2FWh(e=N1V*JZqfh;J9kTJ~wkctLt0VZs zIheI-r-0HK6O)26Q46KoOjlO!hEge}HIwTa?AxX->FyEfR^`b%c~o+u+=rZ*HkNBe zrzk`etC7{UO_0_hZ7kfiM2Nn%uVX z*#*vs!iY+B83(B!AquUvPiITKIDY2G5}s%Ls!Toay}Fi^+d4{U-l!C{`Tiv9V@&QA8T~2y&?(>f52SY7e55mLRj1ZLF2i3dXWXB;UokM%TC5=^)F4V+?mFv&UoD$_ z`Otyw+Po1)`J6z(MI#b7__LyCFzI#+noBt#?>fY2xFU#Lw z(O}r~^1~WuiN`t4F%u3di~jza=_<3p;IYSdc~(hCg-3}0PqSg?XAJ}1&Z%GPEM_YD6Z-FEtS z?nSokkq$_`d~zf-;$M=oaGeRIm+&<$tPMP5%_J-vGIX@_5YTfgiwxgN|c`TN)0X@ZY^ z+0OjB7yne`_uCGK*#8b~cNWQ){9(_TQvBdk;eEn?0_IJr8)kU0a{O%2o5l_BYw%YtKKCwhzlWTil z=&M-Amlew*r|mYKe93!0_kFjC^}i%Ca}275rv0-$7aPIqZ#kFGURS2`6hn;ry2H0t zESm87U)%(#dFr>^m%Yjk-R~3j#=Am%&Xf%9J#IIjr+LfCwf+}254-J?wzRb+Omlr_ zN`Ymg*YA&fDnA;hehOzZHmHv|b};nbEAA8LO773k57_x7a$DoO^9C)vCxV_b&pv54 zKj_~1ZGR`HUwip8`gHWdu#J4Sfy0=XE0o!V!^6(z@_isu5)_9Cy&fu zk@w>3v#oLN+8m08Ke7d9e3^C2UABKNi=tyr;?|YlzedfIC~a-m_Sl|iRFs&-lmA}O zL~&x~QM<71^SMf-9&hc?UAzRN3u zRrw&`l*N~A^1X6WaesQeT90Zu$zED8<92z1>TlEY)r$^`c0F~Q?%m|q|KoOnPe;{# zoxpG-zVD36Yu;)pdR>XnS!u~t6f|{p>D@hCvrbtv}e?8EV5m! zF!{!g;KUy)*Mo9Oo(A7Nt=nCyb8J;bBhN$~8AH}LHoIA-pAuTrVd0~*F!dMnU$-9( zT^F>rr0V=#bNa&16FkXN&E@(2Zu`@I;|}+Ph4uz^VKyB>2STQv@L<+GHT&H4))^0u zOtMhkyuthP{;BbrJt~cJdY>HVc==z!_HWCKC*2NT`oHu1%injE(V{JF%^Tq=_Zw>p zzonEcPx=0Ew&|V+jjI>EGdhqv^G}wM@p)I{WgKU#b3U{m=9sJ1$G$7ODMD@f62lw8 z;YCG%R_9G{`W*1JEC1T;KW_x>B>r1S>6;v3io4J)@0hDMbE)b1P-=AYBM;mE-GsfW7|{jSKIkSY5APpYW{xKX}aM% zuh;lb3OaUZ=S|fWQPKCcmd*1}VYWTY7#`4CFfn8si{d`E8v)byPK%%4aaaE5=Sshw zhw_?lr&!5)Ec~=g?$lzL*3fSc*jTSzu9|;*_Lt+g!lpZV^fq7i-?uELU1aKRz|{wSc4E8RoIm9ggK*O-qYt_dzIM@)S_doJYd{oJkQ%e-T@v-uT8a95D8|GRHVK!%4SjjrBZn=Etn2Z;~96 zGW>XTw$`dT%n96~*PVgG zdlwhQDb}fEe~5fvHYGve`hV+O8GF&fZAV*dywV?i&R@f}aJT5!<&#ewUj1*fA!EU! zEYW<|3op#KXvErWeiFu1lm6-IImwE51qoF%8hY#AF54nf9dYH$o~2$^lE+R=eWPvV znEiIP3xo2siz}BtjG0zhG@WIp-OsM>)D`&?i@2_F9Bw$Lq!r|J;DJxyrVfScFV#9% z{&@Oj&0(v(a-T(eJJU+*IaghL{6_deN{NU#gC4I()^p?h^*@}N&)e<2UZ(nd3-6>q zpPt-w?M<|fiMsrz%iqlMt5@Rrqqe3u_HD4!m}1NO`JFsp@$}D=erD?(Rh6(XNZ**S zj_=x&32O7c`9Ju6uhak@l>_-ab9GPN}?5bwQU??S$+Ssyk&bNcbZyRG4ZH{vM_}|TMW%QH? zbCIoE?%gh`Jp0b~*{n{MVpQ=&hkfk4|w(+<`NSU18ft#_v!q$B~ ze}T!${J+uL&+k|q|FgeLzVxNWZ_TQkyY3v>759s=*6jM$-BYGptL*+L{c=y~4~E)h z+kStYWx2+pGm0LW|?|IEF&?Pss8ua}l?duYtxd#vl~#O*VyCkUJDTG_E7ch|&uV)hSxBucm| z&vM5VEn=2kefnykRJnP;xh>y(RAPFwYo4fG?R)LzS8T;!cI=Lg!PDx_w~sP+ZhZaG z-9@C1<&^uP*aiPnBfS+qvpoM?R+`gS^Y;8cW4-+sb2D<*qy{xYg`UMIiY>dJ(T^?Ei}_ZvJ| z%sA!L%0ofC?>K85TxTB7N%?pB=)4sM?>`7Ml=AOhv-CVmMOfzlkhO;_RE+1FRNgP2 z;(FNEu<3)s%YO-yyk(ykV;#Gh$sf&+C{yy-1mL`yJt(yMun#s?6BYx0iRL5x3xH{^yeSc?-EW*FM%SJCM9+ z+pMh{q+V3nPT%o)^1))K&^?dVsP?_+SIOU_dHTl;(+G`d1^LadZTxpgofW%OwrpQb z?uYZi?mcr>rQBxUx%z#>(ZDL+S!e$oS$|G1*Sh?8=ya)VTyHAw2Oli|sG6!%wWj|8 zgZ;xWzk6~w79ao4V)n34=wrO<^7H01=3JVd_C0^KznI5<_1#iW(xu~fga${HdE4Z4 zuifp^-Sekga`CNnhq6sq7NjjxmbsjCFRNnZiBp%i>IZga@8K-7bc|1tOS$5C@mJuf zwWk(+dm;9Ip2Gihp~jRaxt=fQOuzHdJhSW3?ZEO`33jzzE1a7T&0<}@+s)}S^Oj}2 zJ9%QO4|q5ouU}^*>7UZLTkw*3#J|Jfw}=>~g8RwQJq(|x9^K8~ z@3ciT_1n>>2``?%d!&C(s%6q**@nS3+quA?u12Z_mmXkKRfhES64~e-*{QLO>nw= zV|}|_Y0Q^QorL*T>sA!^UY@Xf<=iPkYqD(2niU*h+q}{&# zk+~=xY_{WjLQU?R*I7Sbu{Ip$;ow|hD56_y*?(+*okqpK(|y_t9q*jE>65W+<@6`X z`|H?m|J-Efvby%(`{U=l)~YSO=$rAo{^_G|3-1U>!}qHen$mN4tCZ|dcaV*!_Y9kkX89%Ae$KLj)()6^&4`{pMO~rq-fH% zuv~ZN4(028#Xo1>42}G-IK7(JqEdMFp63a{B5Y2gdoTBYUAy4X_C2L6*Gs+ymz{;5-K%C~nD zZf#@_eUNdX<64f1>zOOv8As2(IJlzl{e#Tyw~v1PwC8x?@)zCjekRT0+aMNptZikN zYuWnKzj-6`-nJMTpPLqUOY?3o*#1XVJYVwW@*|y!`eoBv5_0A|j+n4m z^s(XN>&I`J^6ulZ$gtb-MqFy$(RJq1Z5w=x16kIroumEPRBYPJ>IoX>_FA5;vhp$Y zP5!2}=-Yi&vmdLLN3Ut;Nz9s1@Yi1T?Y)~3QIdPN%YAg8EV}k*n3dOQLq`TR*?BA1 zoSFRTLejLJ1O2%UZx`#iJ=jy{_1JTLamc6rXXd{!E_C3mPh`1xh9kEspm?VBG41NN z45z+d4cfakwoWk7Q73cX2F|@t?Ri-yl}}Dsd(`A6M@2%0;OD%Whsj%#G}maDM={~%P2bRpkzhCWGY%B6Mm*d%U^_Rg5&K+Mq@2=$OEoLX{<_EKG z3f{hA@uVZiq*hOl7rS_$J-~joGyP_&F|}1pY?r(W?NEZWrnu!h z<2K7J9lGxp%7$v(+k16a&YcR!<%hq!l+9hXYz5cf1!*hYw@&HUqngTlYoBE1K()b6Bf>dvAo_WcU8XdAf#iV(nixJ8jFE z5t93B??&r1h1`c69<%>B8XdPa`xflQKbP}r?($>70RYDMEPL1g`hRLhDK);c0=~TXmL8r|-me^1 zb;Wzv^eJiqk&R5Q(W~~|mwr_HcJ1^j?fr{WdO39_c}}VQJVo``$15A|m7lv4vNwCP z=Z?ocv4_@p{p^d_wKUO}BiUw&^)$(i{^puimnO^>Xj|NwEVyV@R-@h5B!%h6r|T|i zP>P+eYE;hrIB}xB!^xM58?E-;S@21^O!S7-|E%sS55Bs+D>j%mM=gHu8^)lM8{%fM ztvDKVcK`9TEjPb>VqVhSz47fwTf3=$&b3W07Llk}5V+UL`BUV-udOmkBKG{9Z_aQ8 zHY{wMwtWH5ecLm_orNJs)m}YvO1kJc>EMU=zke#MdHvvEaI%S`$mX`Ntruv|JF5Nl z>n`k%*>q~A*rHUu7aMJ+iMw*9y3F}pnqROYhueB`aR280S8v((hRk6-zV-b7y65Wz zN)_y%%erom=`DI1A-Cz+!PL+{FV3C5c~w;Ji#OYWdlx@Br{vTKNQpKsyfb4Xw?gLz zb(zJv+j62WENm=OHkaOW`~ioXzU(gl`n~d^v85uxjZFqE6Lf+jrzoEBy=}BYWtrCc zB`+2fr&OpKzI0WQnwQ}%yM6x2xwd9sj33@;RjAPMKJrX*x^Pd5>IzT8W5Rd*g)Y4p z{qXvV-a)Z_OJ{z~XgzFM+aE2kn?3nx?sCIRR*h$Dvn;Ypk9yl*Jw4;(!UsR539uZs zn569gNMGsB3ddb5o(3nqP)qw!@!v{f-URu_5rVv0y?P7hSiTD9y4!g3QqvEnCtr5$ zc%L6D6T-M>Nj>lC))1=$Tu=YWF+H5$dcnBl(BA~fYhl~>nW*0n3(@-}xAo5>tNes+ zR$<{w%jDPp@@~>poxNx0pUV?oIiFm=z3{Bin&n;x&4j+MWBc!Hv0`0C=Jo8C%|`;R z`CaerJO68wR9X4dJ>oG+*-n=de_Uvr{Pm?gvqu77opVE~+NbO{=ORoU64kucEZ-)~ zJyT6)qC@HPr_aAWocqUcVver9(mocC-p`Ca#jHD<*R1+Gqe*eg>1lVrw1h0V#Q&pP zJ+0S%;Zf~F+5UePiar;TW9)Q0Cd~6P(f^pxM}>(?_upS>clf#0=SPZe zg`aoZH(9p2q$$i*v{smC`_zV=Rq=pnTlt(b*LU*1nKAiBM(xE0$>7Y{Y8yq38JBJ{ zw{iN%I5|1D=d_s#Z#~M;vm^_Jo78EnTRn}rg@&jMFiwAz@w|kuE=w`ciZ&vEEtR?rh zU+>$J_9MvP%W~!AX6Kw#wk+chd3|L8$F9khr+oH!7yaEH_or#8!<{eM*Z3r_*D!>e z)$XcPnh<84`)t<&WwWCnCOk9D*z`X#!2i(+Q=TbO8U_zlGP-=cA1#^Dbo1_;fZ9$Y z&Fp@SDBa0(9?5mQ47J=-dMWYC-t7;*|9h*$ zTe`4+i_;p%#f!p4X4K7!yVYNo$~sMC z|EEtAzFjogB;;?;Y$c{AoX zckRm2&S$8twXnUK^z7N2D+?C>xp}DB$xy2B%+4o1b8ki8bTrPBwJWelOPTX-$%pyo zaoHgS48_5Zcmq1-|94p^A+bVzW36IH`*A(tz{ZXhhh!U0%H_PeefiL#N#Z4Z^PZ#} zSitqa{-xE8j6SJj`8SpahTb_pu}kb)Tj){M`1%?D>eVyn2P}~nG)({eG+)-xmgAiH zoc96BT*c?qA8#~HynI7MUD#`$&gvy84eIY^Uzxt>{zdzGe@Dl*1)4iOO6L@B3G^*e zyc|A7!YDd|<=fIn53;}fTcTmxTRC~p?2Qw@Z(1cG@P7K_ze#~@`#&7+%xs@~=3Tf& zVhgX#v&*GBePZu+&3)RksY$M&`CI>Q1J{GQou4ijH4+q(?wPN6d^(q;(|wl1arwud z>~T)r{ek13Pw??psi!aHPCKT)=fBwUQ0eU1b&e~{zZ%P(iGIJ_g!R$7b3MkNFY}qP znOnQD^;kT8>uWVl(v8Uha>Y_~h@Rzd0ONSxa*{H+U83*m9{h3Vv}Dw?BPl z{=X}QlDg*CzJ8iLhuJ8*RimV>&sk;7*15B`di`hJBC+$}B@X|doqOF&+cr6P9ZFVO zpIv~-xH7$52$Z&|K?Z~6s;r!RUp zCLVkFmRrKHA@G?zZx4Ho@6Y~f(QjIxE-s%Lx=MY??!bnNVLHoZn?Bq--RV#WPotz) ze25)q`}$jtllNac#igqFm#-uivK3Jx##(SL$iulUF}2p7p;^7ctTRFOv0tuo2kvKlW+gZE115t^N9bb_RcJ&-Tb_TghuKF45)xCYD9mC}=&Jk{?uB^ZezXHy^`dPak}gRdq#! zJ z{Hm$zJNv>Z+8sYqudL_g`ycnUHKiip*tvwS{%>p)90Fc3YuuW)SY)|`xB>eon-xr= zR_CKFrtaEa{bQ}Ds&pMYN9K(}X`@B1(~Qc09uzzoHaYvyrmf!pjP`9?XU>uEPqbMn zqav_(Vja^p9bvN<&NKUl z#innu?h#H2iHz)V+M^`Xc75tcg`3|`^?6+07c9H|{qG9PH)nazmDXo6Y~5|SX{XS> zqxD-$HowxGqa&X2PWd{1KeGojg7U`F{~jz#_+eKNeb_rlMpam9=4-2y zQl>I>=F87IGt}D8x+R>mw(;PMCnwW8EA%v!_Sp3+NC>pK2Ia67?y8!o5Xc_MsvI)= ztz7!%TW?x9{+s?5zs>%Z)&6lRvjV@KY56<7Ya^CM#1O8Eo1 z+_yL=oH3Z)cjdy0WsIqxvmDEQwl&O`f7lxG`eev%tu;AQ`^3E#SoZAlWIFIt@6VT# z>c6I^!dP#`Fm%{m-{aoscq5ltf8q4|Qf9mCxH^}pDV|)Rt#l{4<^W&AO(ByltqR8d zQ{O~>Vc9xOU+(_Dh56E-HHvvO4nOp3;a=X11)C z{*3xe3-X^eJnpmdo8fH9Kkwqw*QdYad_3XLcOh@DW>Qqnt4@tw6EGOpAVMo`j zd2{Z{F@eu_{_${s$>6bWS6?Ebz$VdtSnq#rwnoDY#{^UR_lvexKPg^&t-Wq`Y}P{4 zi0M16S6%*ky!#AqpxEEXLNm|)`Ro+;_h;?Nw+EDTOaz}#zs00~L$p_5Ucuqhzu6md zeGeXwTD<7hKJ5b^UtTnEP`kBdd7EC`gsVTU_1!aU6#Xgpw`**zTPkBk!biYn@ zpSp4m+m~j6=i3Zz0sWyiz!u57nI{ktIOo20hHLYK&}RL@Du zJ0)9rBBlmk7TjJTqLC_XTJ85gRm8L5uxiJPLw&}x{z-}jPPEJEWIJrE@b5<3f?XkP z-rMAg7n<;-o!U7)?XAj-nv>#5{Kv}E){A$(%h<%Qw5i5j?a3-r{p&hur&*V6d2)i^ zvzuk&-A22ApL3a+)~fiqH`#7l6tL=n@2bi@+|lVrIL>yQcrR-3K|W(g*rkch0$&ys zr0afFjI%yaoSW2R&DN<=dYUu*K~`MkZ>if;FC6odGv@v4!`dC|IeBx2>LQsllc&3y z#&1{pNHnal+pQ;7=W z$?jdLsXlveOc8EVnjQNoBydaWVvbKWkK5Ml+`QzVzq4J5y2UFyuHvjW^PJDvy^vwp z{7T=ztFGNo?Vhl2>U{QWmKAY%MeaL`uI)bNkUL|a&nx2}hlE&fGxjcik;)j6w&#e# zx((jvw$6#sIe+f>4z(9@cfa-aoWEqEBcT0Xx6#(HrF*r{N6i!W-|D_fp2M%JW-;YP zSn+(RHAO82SucKwT`jJ;bx)6dx0+V>jTcj19G`uDXYkIPJ=3O%+_V2ZS6*xSS2Np* znjhWIvI4%vsR9 zvh>s9y9cXtLvOE(cYb60gePD(o84?*{)<)1Hn~f^e>Urnek=2fcaDKu|1Ha_D>hH@ z-*jgEr_Q8J!H-H*qK?>UZaX6xz`3EkvugWPOD{1)g$?)A)8_qkIN`h@#I1RMF-QBK zM_Kt9SI&FZ4|ywgpWAbzNX%`KrR0Vm^6q?p^53p#y6i~f_&p`;RioNRZV~&1T@%#R z=Y?m#JFQr8PdT>tQI-@+g7OCAaJ>lOPYZwgxGlrLnLSA0e2{7#+iKJQHY{P?aOwB}x0 zY7$}Xs8jb@@$iYF{CgdKTYjiq;i_<}Q0vru$lm^BruF`f3o4^s&ooTV>sYiX(ZXmV zd#~#HV24)yKC3UqXLD|r?nr!Jo5Qfg=zqh{38kK2F3-AdI?1HX?P<-X36*92*&@Xv zEs2LK^nC0+wyX(>HL93u#KF+C)4_x(PQaewQ* zmHIPJXfmu&VcEb@QMEQ{0+)_=(VgXQ`V4I!U1GTApqW#Dw)V(@E$P$cx2}1Vvn(XB z|JKX5mY*K`h&wNZwkJ?WS^oyG%dx>cZr`QFRUn0VSvfFY6 z1GOz#=e!D*Y`h@5w>S9OdF~?Tns)PypVRlwQ1zL0Ea8g2{hC9YCv7=A?eay7=iwg@ zJ?iM>sHy(D=*@kmM-v4U^eRqW&N}hRcv*7q``*W^KAYd+Tf^e(B)ITR&W@Yz&Rb$? z0vVo0o6CDX`S|=`)u**{_*Tum$h1{PlateP(J7|Z{1{!v@0-k6E`6WR`e1GO)mf9i z9G!U3rFXfg4DY^*Eaf8(P0`6g9C@=ZY1=MpT>Qt-E}WUI)Kf3*$%iJ`aj-h*-V(a*6*6)4g%joN z=^hMcnC|Ql?&@v1mu6d#v_5T{)Q0Y&J#vBi?d&m+j5qWAQr#yo|IeX!4lRo+9Zel# zDwlU}(oe`;r=fV#(qd~r_ci|M$t(@}M%Qo4SN{E^G^auBck~)j)5rSX#YGh(uRT05 zZHm!_FRQ$xKi3tp{||GxVtH&=kejUf_k(ZW8kCsV1*&A}9l5)gd)92RSHIa)8{D>> z*=qWd^Qf^pUuieT%=^#&UT@i=rflk3v0iX7>jDG8`nji^uZnGa9CYf-y~lGT%v?M> zx|gfIIm{m*A+i4Yyp1LA+?HH&Sfl^+OI=8FmgSYI-yh%aKceV&bP9jz@uV+I`Wwq+ z6n>n&FvFz5!uEnG)6vv*d*!>dMGcraPgQQcS$W@D=k&&N0=C7q9&O6=KZi*^$yuGf z@2|(N?oX$7Hia_X2)lc4R@cNZ_w8*-$tov>zexT*Rdi#YQz65NYYrzj>rVP=(|5?N zLFG)K`ZU(<{v|tuW==ix;>#PO=XDJmeD?oqjo35$-{ot2_Qq=luRr;2{S#-8v|sDm z+F1-|{I_)tONkH-dVlpp%B+;j%hH|N*IO+2ef8+@uH5E?l7~xvp0}@fe)fHmvDaSS zNeewhXKqP*d#zxyMN(-wht;AVGOQwzJL)RtJ&AnR@_f;}UuU_tER~X6YyUL;LHpv` z<)I&^ADegS&!kRiev!#qDJIp9F#+C!FV4*n;xGL?ahmJ2Rj+1+|1o~QqvOEJ?)P6U z8#i(9pRsLLZ~mSwryqvJm`bZWn{!Lt@8Il?wOL{A9p@%>Wc)bU6Y;fUZFqKE%KZ?n zxWMUaJ!M#VuCtx#R+)Z&x^eCHgxI6Ef`z_wOL$tJkeZW!z|7%5+fPd&jno+FDtUSR z!-c;Rrmgp#9L4lz)hzEyBXi}x7QfG(o9Dd^K76NTd%_m6W0!wOY;N~r=v~arR(U$< zbfk~cz6_HWza|~4Pr|Pu#g>L?bI&4WQyUv-+ty|l`c-S}NMU>LV6W7(sp6kC_ zvC(;Hs6>@CoAph(!p`7LEw%}7e!ZUbt@79Lspg9W-c;V{m+Zg!Gu&fJf++W771jPt zVi9XkSUk8Ur()G7sL%iQx1M?&KB}7&s5B8*Lc0E;*0K!3ypj$KLq{|eEWTQ zb6Bj1=AVZt^;2@~YB%n-d&_+F-Q651yO&I>d2KgrKYB)lX+nL=+W-&o}A*&X?c3tKj;C3-b;$J9zHcXU0(1_vmw%{?F6f`rdqfZ~f}+ zpXX2S-#&6+RTan2pBM7P{AvnCM9jFHX4^U%r0S@tCdhHeS1i7N*D|pt+L6iDP38g@ zhvf%u#sg)4)@iP2eXxnc^LP8})U`dF-?o_+g^3#poEn7P8EY42gq{nk<*<|)UXu*uh+x43!w?!uc^ zRV#lm2TPh&tDRB$#`kt|LFV@M&o_E*g*t!V)u;G7p|W?|gQIhqzUdjqtXYy(_OWzh zR^Bs?q<`EoEE_y0nO@oFW%B8oX13(Z%T0Hb_UO53N|!QNem3HHFW3`5>&@A?)LYkX zcXd7WD)=6#VYM&8aO%TEH-G7g++y2atdPn)vS}q}^_2WOx{D%m3b%fi`M2Iae$S8F zbMrL|wt1dtIIh4UBc)<>qjAA|xtlrW)7+{ycEn|s>~>)?+^Q&A;I0>CQElkO&a0TU zNAuT{`g*S)k9v+gPIAmCUh~SZK37xTVe*7Ss-I6z6b!nP%of6%w`;P=j_Ty2!C&6Y znIu+NXc4HiojYoQ;(05}wY$zvEV^1HKmDzsz3P!;AFl_vT@hzd*GW}#JO3!+9zS^sNxf9H*7sSaLZop{qZeN~5!)yy!=iKafFNl2Tr<=b0tDnc!HEdeeYA)grCZ)&U|1%Wo=)a;nI$K21H z$*ySEn=I`1EM zTwSd=Luird@rUtZPM?b3?a47Xe0E0FhL=Bf{fJ?iH&s>6r`Y4p(m&6bq;+32T7O%A z-ZJ<$v*moZ&=U_0t{rN2`_bj*l|vEDZ8?JcixKq zJnHkjh2OD-y!qGG+7~QjdgExx)r;90T=qMsEr{qT=!)-MyNgfc@2!&;PdoZ){H*W0 zzByGxm_xWC?@;*;?$?`Fhjjm%|EuV0MXE|<{iL9O&XRMD&hGgf#+SQS_+OFc#h?G4 zulS}Zx0j(ko#$SE<(<`QLfym_qpF^C1hERI<#p~}5Z>c5J1H=z>(DF9-$q?2QzuKN zuGI?H)8BAqGk39c+xjptssEBS~~;#Zr!&+iuOYJFJqu_&gA^Ca7M@pW5lnuVrS zXdS#bf6J~fhO%XL{p=!Bn zZ%e=An$)ezs-EYL2tCU5=i->sQjzeb(a>z&)R4@~+@G#8T9=d$E9r&ZKlW?e=K2Mf zir;NCp6=3?yfIStz}1DlTP8m5eTIE z+K#3f!5M~TXKN>2t#vPIJ6u}h8zyqtVPSY_O>N-*`<@Bu;wu z+nWDpRxDZcJM&C}9tR7ryY9{@okwKF${!@;C#)9tFx}Ssz0cyuggsV`KW0ZHN&MY< z;`&}&9zE|r!lKc^`s$9%+8)OnI-V=H39NWhy+c|qO=Z@J)jnb~6CSL0JN#*_t-h)& z)5f?fwdbrKWUX80`fYuAac$VuqyI%RT;u-Dy!i0gQ+6}+1OsNhMe>Ij|8>=`-8#ip z&r@ZZrq(C5)8>X#T5s?=L^%|_tq*Cfe>GkGjUiX^|k{csloSM9xW9pjqX3mnj+vVOKGX2AUeREE=ve3)i zJ=SU^g)3?T;(e~(X^POC)5YQ9a>_zugPGj*+qM@9E}2*+W;kbM%!w#lP`~_>os9D; zrQ_$UJFfG;|8By`dFkgphrCnUbv4gx#~=Lgdha&*1#2UMva`-ESDyM=>a<6?h|vpVtzIily}e|2w5!{^ zBWF0f@992~x|^sJkdXY~FKzt1|Ja4* zxP@N+AuRDMw)Px*oUIJnvX4rqRQwNQ&yBf#!tK5HDaEF>*TpvO*}T#J{x7yEMW^pB zs%^WiTB@aKd6AK2n_;k2o@lr3f6F61%#AL|I}0Zsld_P%^z&2qf{VMew@H0nv_>ZX zl+2PpF`gUZsEGf}n8W;5bOJc1pwtx4t@SOM0%0E2u zwYARIn+`Qdztq1_)DyLQB@2JYrZ)#anm*6tJ}>I4&69T1-%;w;r9}o>J6qnZzdgr8 zYIBQzunNoENwf5lqumWBzvMo*weN67V?_S5W7qO@&Lo`rT(We%=-Z`XvEnCww}d~5 zetmS|qAs!3YcCebb+z1+>{!kl@*|fk_4Xdwr2(;#cbBlmp4)m~Yj?)9wDL?jaYOH} zpXc`e=i;j2Y`;}umSDHMwpn7c@MX<+vJtd*8U}&Be^JfBjvlHkTZ{pC@~; zT+v$FaZ$D=q~NqKe@SUL$M=7d)!hBp*>~2lYfrW{zBJh=Y<4-9*OvLGoD}od=cN5C zJ$rDM@R6G`M)7vC4_aqE$rRbCvM74SiYbBf-I@M!EL3ULY3FZF>+g!lN!qbJV4>)x zzYag7%d9`xJ$}seQ%#N~eBGUM49*)KJ~TRbGNU$u#blb;s%7l{zH9azZrZ-{YTOzr z8>5*TzaE9|+8=gW{(tmImP3C(O>Hf(|7&5r;mxV9jEj9mw}txM=q|12F^!10cI|)u zkzk1xR>sZZ8;qVxUvW)eHhsOy50RAD$}4QUFKaC@w|6+QbDzWgqfHgva!C=VKHpoh zOnj@|wmqTqj7y~U>83iLo3-{=%hMgK=Ibm9XHA{H<(Ss4$WRw))653NwGSCCN_m{n z-K5&vr*Y%!tH!u}N2V*yyu8D3!725P$G?>@sjX35)oZd#u9W|zV(@|(eGi_jkZ=1A z8r9c+{aB`|FZuFpqTRjw4AVRRXaC5|6zRI2vM}swx4;Ia#kyLr${(+3-8W4x@|8@P z-|T}A-<#|7{fJB`9x6OZ|G^%+#R-r$xK+%{vLL)0>sTc3MeS7=0P z8?W}4X6^a6>F^q@b)lxFt<-yL-X$l>1+fH@LV* z+DNdcMOH_-`!KzIq`TSi-pf->XI8ORa?h6R+rz_llA-W<6z9o?g__Uqp5%J{rF`w2 zS!~s^9^a~c{idI9w(Q(~Z`D!dC--t5?zy~FuT*f#4lm=pojq00wm!S~cgdrwXXOq&pSr?Xwsr+}kXxvzRru}(wr)|y*ndU9`Z|6-gH!aHF*r~>q_u<{< zdnc#n_C<3fteM<9BY9HJXHr^d0v78}zY*IehkucCkP z<^pTg!efEaR?VdaArs9Qmzg^|uc|WIrn>*#c|EJ%^RbE7XB&GmsLD;sSTe0%{LkjD zw^a!*w;IJ=`fYsuPJ_>f^@n&u{+mg7ux9?=Yv;wPBNMl_%}tB`-) z*SilLOtYI}wt`Vqx72xZ}>drZkKn(%D#DN)g__ocaNQl zYjRczWWGD&oW2$N_lWP-DKj0me29$9@bRhH_;q4nmqLZjJJ;stKOB#)3XHiMecAB#Lpc%7n9A{?}3hF(-PONGFzL@h1mTxGTW4-!1UcPa|AZJV8yxM=Zzwk?kqtoyv#Q2dmR(_x#%hvu6( z-DA`<`+Rsl-^B)oONr(svoHBXu--eTXrLglCH3L8a(UyP8)16?ms8s7YTW}-n+4ko=?Xmycs%|l)%?nttjW6Y2`P7W}yaC%Z9eivK&XS*U zXaAvA?b*)nYjo8j&R4$kO98-iaQ%!vO}Zv?RxQNMeK35 z8WSCOkG(z{defepHAk@b&w|s7 zI{oE_wceMKf-|jN&RC#-DrT-;=95o{Gi&*FsOm)=`lgcid_nq#TNk}mmaGl14`BGY zc3skMLB@5}hH=Gn64UdlmgcYdaA{^q!@V8@!v_~w{T_DxRjplk-N^FerxxX~Mx{0J z4IVFl>L>kn$i1_?G_U&P8mpOWFF9YmempB=?z}P$PDQ29x>j1|q3rEn4)ZKdKYQu4 zNP*_nuO~M!)E7+pY&;`(r%>o$HC~agZ#{PGv=2^hSnYOqNrCUl1LxlFnm92)S@!Ie zCJy#BmCw%Y?stgs;+Os&lvwe_x4kIGCDpK5S-yVh+KM&Ll~4K}iZ4&S`P_Vq)Fb)n zlesU)DjjTPmkc=)mvSUS4u+Ft5D;=PnWMpAAb;~!yx(m z_La*bF6O_a9zN8WxI<*`i_@VI-#M)~=PzindQ+hkSiMS;u?4iD_&jq#MaS9QX8f!6 z?&myQSf%mh*e5U3KfWdVXWyvO`g8Nn7cMQ2>c6*|Bu@2*FZ-e?IHT6iqQm^Z_qx*Z zZr%lzLD3dZ#r0pm;ohnlzGP2dveRlkrENizm(8m!?^($>x#h)?RL=7UI-(k$rcB;A z?_^F!y`fL>smtdrnO83=zW#8{v!89>^Osy~eqVLEJ3r>F*yN)1kz)D1>N^zW7r z`VxP_6l9hw==X5(*_xfQmOU91nX%$XNvfUPTE16#yz^5FPX&kZclpZQ0@E*9g7=sGlRWllt4U@e!=-!Dt`^S^*sX6djoYzk){H~< zo}TjUyLRAI();^YSIBSK_$O~hXu_G|`GIr%c`kLfElLlW(fC4br%CW{hXMm;x3FEx zaeN)@P0Xy3OfR?839YOTZI}1CTXONR;NwVpbI}7PVILJeSNc@h7zSld^liHMn*UMA zxjsz&|ysky33hUo&fU3b-+|Og zCdv$!yX20a{{3fdy6Y6F9oAt+)9k$j)m0}{WWPv?&UtH}Xu+V~G3(p|{V5JJSHJJQ z`F`7s>Faps&OQ7$Dr1Gpu9EYdQU1@O8MmBmX3Z*T$~x}-)AW?%f?w`09yU)o#UZxy z{L%s|2Ac?f&nCCF`&ue--<=n4{_>$vt@&A~%bpo_I(s*-Rxj9GUA|?qhn6nGM>~(x zr*|i+2K^7vbdarFZ8vS9iK(O~A-W$uW(7P z8`G?$zVBKyj=b{|Iyg$>v@BuV4IjRNcGm z^k2T4PuE_$+h~@*e|ELyi%Umd#{HJc`&PpjGQWL--^-P~>N^kbv(l-EIPceE=X+<> zs@3b*W*`5gEKs{e@te%?y$s?HR%tWu`nY}ZqMN?4b3Ytg`LHyXW4+7IsUolEuk(n1 zYkr~oO!lo;g$6HN?EH7mjsFztAl$6GkB66AImSx#{lad!Xgj?>(-n3y_{{xgnpJr^ zZ@tNcr!0Yw?is#ynO4fK6!zrgB;NX)r#XM@*)mnJqUXb$$W=O}f}206@#lyn>^(I7 zM4wACdyO|6KFWdhJ}& zyzP(LtiM((^;`FxG2A^*T%<5K@OfLY=bDy;_L|1`(Yv=P?B|T$#*~(8*XOdh!Dold zvdQW%?<{%2Wp(_Hr~89F9>x3uFk4=XVR(EICOSJ3`b=5ApJYmFDRuk_oYxyvk?<&DGdr)ka0mR|Ea zV)4x%b`*#R`@H}Kdkfrvi^Jj;k0>@8@Xre z&y2i!#nhDJ<^*qB?RI^M+HStD!V9`@X>Mz-=ljBQKc~b%iEG!rB7vrrE*#gq-pGsY zD}2VjZCY>jW)D}6pYKwB{CqR(hwaR`+y0{09d;NUxO(iK)m68sMU9yU?zB~&Ui`-S zQ!cZo(yiZnxqa?BZ@B(_OUz-mt#O;yyS$jw%WAu`=VWzV0ye(!m7 ziTWZVISZcuHHVz1e|quveStu)^UiPKxkouB$o#rJ$^1~x#)&2CUQST!Zj`(rV5oL$ zx1x($-sj`m3!D!uz26hT79_ce*VWT*&74I{PYu4uSoM9H_VAbF?SsVve`N|DdYG?S zvuJC+WVH4Mt|NlMe@ye$U)|Sq<`8~yQDW*-el43UeWn102YJ6*Z9koBwg{aPa5gI0 z^Sn!9QtIiM8`wIXt%S-J9@uteW`kgBz`A#fOum$GglwF@{pY20bMn4FWz8lvtTc!&hSDY9A(sqZz=Y4{d|$_t;+sMi)!V=p6+eCz*v`f_tC_j!(t)@yBCHo-&(y}RcdG6 z+w&Ke`t5ez`#vh_SWUX<-NhA`_WO8?d}i5tJ^SvNt(|*gywZ-zsI<@G6nTH@vFzL6 z=+zx9oyUsL_J1mM{OnZsyG-}-qv{uo@0_n&OykL~Hu`3_Ha_IJv-^3iMa63voTp9Z zbVxs4(sOs2(=CaMi&uIIzF8_UQPm+*UoT<%+E_O;`Z!@lfrm@wyvM-cW%$tt<#=n9XOmDcU{5$tMHPxnBu1u zvmeO4U$IPk$1FWA-71M`f-m>~&$cxCc+dCi>HGKO&sREMKjgX3<@_4{m>hB4j$2Q> zMIP!O)>7qe7kPd0EmN*@H0$BR^Eb5BtYK3VYTu_7zg^Rz?4^O-cSGuOL2 zweL06A8vZR>fntg?g?xbEZWa5b@m3x8k|37=)$H`S>4@L)TS+H+<&uti%{IcNuIO& zb@keVEib+PB&{c6b@|Sn!=j0iTZyCeY zIV}YLa6Vj9Yghhk>#K{q=6_3`+mh$vB=;!ghn7RoO_iD7e712lr?bROU1uqkz1mg# zz!aaRque*x6*oC5&wFxJYFTbbT5@f3^1Ymb4#m~0<&IiRk(qHxT>Ow#L6V5=oaftw z4{G^czP4oB#mPG7{wF+rDBaG=K0|TuA%~SObNugYIiWtuZ2u)`D=t5?Uj|n%uYXw; z@x^`d)Rv#U_j5lS*|hl8uh&`aa`Sh^h)z5IH7P`L``)*YWff1|=hb3dmtrIJ=+l+B zQ_t-WJbuPkD$(9vJ4e;oW2XPDpKr{PVpvb9e|Y?S7K^D@yuqfA;&(b?4PElmdZtBJ zq)9os8IVY$Fr?;pvmh@|?F9T-X=b#x{4q)|Piq`|q9Dooyyi`e^Z??;)+{+zo508Vthr z_%_+COltTyY1<;FsOnilr>000?Y+OW^=8*pU&l_5TDACd5=STA%{rH#mC^T--I8VE z{k-S3uBhqm7<&?iZFkmlFkd@lZ@TywaE`!Owch&p_RnOHn1vv0#6|ohD zv+bENL(;A%JD}E8r`4c(otjv%#M5gh?5-LJ$f|QscGY;^9_sA0`+^~Fbpz}Cgf%nD z-fvjz5_M?pEt9RALak>6>Mfc&Q6_l#+Rvu@3S!q=#J@cqJ1ywx%RF?$}d*HEqx7{t9P{JAN9+$8J9RtC~*%X%BG18=c*A6D;?h zyZz%{+~lL;hukK1aIapW5WRoi;Rp^_v$+~~X3bpmv?o94>BpsPU-tBSZ5ItLTxFoJ zFkmD#TyqGeta1?_hL%;Bg?SQ9s3W@op<}a-Muf&Get86W+yj)HYzRq z?Z|AaJ#o6*j0h`_sWIvER-P4#&Y9IVQ+C44i*njO6 zd-6pdY&oth;?)>c=w8njZxi(T+|6YHd$I~&NzJxDK8t(##r(VLe+Hx_%#Mta4&y$*L}*|n=Agy-}o#gE8{Vv z{AZ$@Q@y@?Szf}*?g#IfmTx`!if7ql^C#b<=CT%LKe6^Z(EaM9)Oxjom)9;n5IvTz zx>t4om+xxKs_%CT@IE%0Y!|;#OWl6jk5Vb;vwP0W`uoW>cSYoh3+gMKESQ7~pL1`m zmj64k!AJAfDbcd(qSt0h>7>Dbr$C*Pgj%&%JuG;h$i zm)Ih)Z$nyH$ux(}x7)7m%$k%m?@W!6RDq$F(B646n;K(2KKWA}xojyHYsLD8BaW}- zmcDUxG`!0)$4-9z>0&XVLk0bZ1hw~Xip=@+Kwp68+zEHK>l1cHfBt@6C+6d~?OWM& z1=cJGSKX1Wm+)rwq2mK5b*0rEFF=d`_OtNfaC$HYt6#P6=z3){Kg zU2Hp5r0oU&ME?rWvplz_z;*kHgt?Dv6h9^;zUkR-JjGh;vcO%}0Hf#0d0$O?b|fS$ z*t74-mc+CDCm&8J+0XTPYoh8K ze%wlSJvI0C>k0BD2aEnD9uvG&w7L9M!w&mxKh8Y3@2~%1(q`M~C$v{&pLA$DB_s7f zSY=~-b;rx=_4lmjzvh@&DY1sFTk6xDzz0t*w6Ay?S@8GUqkXnavWgG6Roz9H_oVKq zGkRsYI?s5?!RCcrf>!Z13{!0PgkFlY=M>^QCacH!zn;r0DbvYK(6y22c5|Zm&dzt6 z>ID8ZYHw7Y6&hT(jsM;j<-l~NAWMz6T#qGZ{4AcdmbLJB!-UL&Pr9%46jWBH1x-hxR+3@REx6J9jo(bIwG~#_2O` zH^(fJI(mr9`YQjEXP4MK+b5;Vn|Lg^lyj?8`&$L)5><_)d;hlCAMKgIeO#BBBVgCL zpr-JLlv~(KUV=a%ZBAdQ!`iZ$Ixv9sN?;^FhD%#lwvc zxG&C(an(s--97nL+p|uuN0*lMUzySD_o;?I#V1)UfA8BL8}D1*Z_+<^FXE1)pN7V& z?G<%5g!*TA?wz-E&f2sy&P8{$tDiqzlNYpKyC`L;Y`RbSlLr}3CK}J*$FZgA<)WIF zwf7Sj@!w*3);4P%kIBumryr>)ZZ1%3;Zj^AK3g+pdGyrFnWxsZ@j4!weB|}JeHs^6 zoQ`L)h^@-{dG(5r!SRBoRR`yMYR~z%)<1N*>B*QG`AT|vsXE)uyV4Kc{CD-u%&IK6 zw&eT>5I+oN~=hVzFpTrFQhu+@D2@ii&?|X zalgp5JTvN|mG{n>+_ODmvvuU2N2f~H@7v#-e6B?J+b{WV4N9gFA-i+mY6$$Tm~MV^ z-OknTCciK_`SiqoCg!>zQ`v__M%@SAewwp#TSI%KS>gEtYwzFcSxLWQi#Kn%7?97I zd1}SChI=!ZFW(LmWvemPn|8&~En%1QSMmk}ecVBhV z{^7HF4*LP|ET4uH)m&xy5(T;pOT#?WdnE2yYGfp4vDm=}`P7ksP<^ zH_Ar~m!8o7_2vfCOsRmcvikaGvNr$HxO6;tSx4=~LXGN+miw1qJ$iz7lEZ?`qVIc6 zE0%4nD2+OO_;c8My?I+Nr zecs2obHZnqTYtm%2x?CEF8IK7h42EB;BLhvpt%F7LRT zEp^8IlihjdjQfgif0w-*8I^YC1q1Jcz-Wb2Hj2#>_2P$~KP}D3zv|Xp8#~8TVg2P@ zdYdl)N(o8asUoMJ8+>2vDu2Ax`ZbFR|0~+9_|;j@ci?p1ex-Ec>TH$T zed-+?j@vC`BeS?uUmsRoFJ+?25#{oV*Rn(Jta{!Y|4UJa?panaC-2}5&basBdO!QW zdB;~}ugD4Xm|-u)ywxZ!i*fxg`K-WD!xfwlB@9chK5CIjG@Dp^W|j5oBMDdT#eNBh zyrs?i^24sm&lz(gg>Km8$+Lfnej>?#eoJzeocyC>$)=%iCz>t3I<@lC;RHeP0~4j< z0{#Zg`IGDTBw_2Tg1fa7x6e9$Deo^!joXIyrR%q;p}y*juhpCaYWwOIcXlmOVlh{}{lg$Fwcy)w{$1|%k8Vx3`dj`% z{@JW!|%1JlWwXRI5lh|*WO_-6sLIX%y|_BwD$Qip|JLgBg>koeR&Jf*u-7(*tHv+qxb5l9w;Ia* zmOzwC1KFZR(A{jH)Vy)(@ zS4C@c1s8Cqam-)bUU0*W^YZicGxY>Y71hI=PI7(lIDYqu$|HmF_&&|&b2NADoonas z-^y|?{etmn+s4L1gYeM$lTw|N4Z_c8_V?RKxX4XWxD+E{zUuFWj@En~uIuX;Za8|D zHQY@>L7ibqOl{7qW_L5ya(|U?HV5Xb#h2Y#`n|D=AXl^@g)RQf3J@;nU)BTmf+QzCsr8g+IJ&-T==kkf6KIw9lo&l*lxFyq}*o(+6H&;7obqyo`e{8!#aH5cG#U2o^m@K<*~_f&D<}49N-yA(pCsM0 za@T`zw_AeOCS;kj%zd-q`=6PA>&))|IQuH`iA_bFc~NrZONQkfDvLcHEzL3J3omPY zC*=DwxB6?GZ&-87E5QIcv8bsg1AFPgRX%Y>#D%-Z&$W8w94^-c|&RQmSl z9W!`f$G7Ri8P@|FW#^R_->u%uI`!K1n5xMUQ&@krRLWl5pAwRJ<@E~3=%Q5*BHivR zOJunrrm-~UyZom2Tr3km8!HQ$c0?{blxSxynV-5z&^6Uuw7F49 zq4A;XyJC~W{15YVSI)@Uw8_%j!zyd~k@VY!>$HO3^56d*R$7|ow$^K|^-1H4{=#Cr zlyuny8V*{$nmo()jE{x-{?i8GGYk}8-&PUXdgsNp<@P3zxKA#wyp;PUaP}^D!IHkJ zlK+{H3QPEdSmpd*GT*%O_QzBEr4!p^qi$r*J1My9d{yavy~yu+4^EtV)^PFqX2$Jv z{wIi^nVOh*@wL31Ut?d5xV9nBQ>It@WjQZQ7H?efbm{C+myav_C475totz!{EiH?0 z`MY`i3b&^IH>}dkUURan^})ww_MK0gHht$>*1jW6Y5i=&7ngr(D1FvUI`;N@*n0ti z=6N|gRvo=8E+3#AX?Ran?Jlh zZ&lUN%d0M^YWmz)S>q|lTl0v8rSROs{MbdWX9n!zVU{)!`Y#qO8gpFY^o8X!Jb0%k zdF+hw*|lQcn)8YeC%){SoEHAW$iQ{F)lmoKpM7gDODm~v`>*wKR)cJsmn+-fu64dE zX4+MR{QNjO@!kggg`8Ksp2zzxT=)Hgyr`u2&S{J@&xc!?CA0h}ZPE*z_w_^n5rqw` zhaO6D-r2NFd1(iqGran6uDzk6 z)~3xjqGxWuv*m=W#q^_>&LqFyQGd@rGdZVUNnImUJ^q*A@fS;1{S7J$S|V8@7AJB= z|JYYqizgR%$%ac!n-O+WWmlG`+m_QCV|=T;AAi<+xcm2=wpp{5MPzV= z2?sW(TW;Tfq{w^m@ECo$IB^bZCadtP=7y`~szwVuZy0Om^km$;uXs7?V&v!YG!IXU zeQzZ>bXg__?LT*fzkmJe`BNo?jnrb_yVd)+=UVCLgy%WeO)^zs$joYMeAP9}=iECJ z8!g3+a;t?3vz{qO-serW-SR(*@l5vxqXmcRqI7=0$aIaEdi|A?P~)lVOak5Y(`Mc~ zb|UhAoZ-ZG5&tp3FCf-#V`qJmTl}j_(5>*u^U3W@Y!kQrSuCnw`(|^|@e8x-BUyX}oqs&H z^ga1sr}Vld>O1j7FQiwBN*M7gWOs0r+x78>*B4KYmR>kdh|sl>Vxu!*_WD*^NS}; zx?-(nj$Jw4s8uw4@PLH_e++`DgJ7)4y#&u>g?2fa4En=Ep6znB5 z{~q7N`x29Fes;e-bMFYBq}W0~4&fu0lM;kZ{uL9+zbVDO>zu@Wqw=uJCGvaw%#HN+ zDO&{CgO?+EZ?;H8FO* z+@IyEpWimg>BO?)SccyMEBrWQULAJayW`BKzvZt_ud2Obu<`GH8SZ6S0YOz#TNDn* z^{h)?l<@W%BX{wPmvM1IT|w@jUG6DxZw>Z2ccfFu`^w&=cO~Z@)ZWMqv6}e)>=RqL zlR59!&DbKJbok;k&s7^7-pC6~%6|V-&B9=kkjPbAxht-_HU`i8w?@-<@(YRD`?rKl z87Id4o%4}5qTMTI$sWF)I+d2(EbiIlYh7?`esgp763Hc= zFK%z{KGUdu{=_aekA>1A6BWF<%=KoiF6wJn>A76y=KW@qTJ<;Q8_Fx%k9^t4a`9tx z+x0o=XAVghUOuFk#-+3CR)WCMwTvEYr?z(3OYpy!;Qg*{FFJFI^_iUIkHoLMW)!)1 z#P#3p((bQ29J8k%az9yNx;5FU-)8eE+a;B6lM0WzOgr#)%fs#m)| z=k7kE&OlE$E!}pn(;Zs3cxCU1?oYW8_@+@SSb^u-u^UHbJ~dqy81o`Z>aq5Of|?cA zQYY@Vd?n(Ld@@{7DE5K(7vm<$v`3R?o^kgP+vxWG*Ov0s!Uxt}cUCZ^mFsa`ur1sF zS~B{-L~7bn&*FyDA}qhRednIaqp31=(Vf!lW5;-oy|-(7B7AJm6UEg_q^Fw(bgcNh zM$i1i)RL^7F(*FWuE}1p+;M~bx!bw!U5DHZU$p$W^y4U-^+>)v%5AT0bn^ z;+TY;zX)$`J?3t2@7J;iHrF>9 zI?vNjle9VcMs1z{k#BRZEXc9H^I!40e&-Qmy`N#XOm*X>Zuk^$ja_el6X&;lb9Uf27X0IDCIuzqfSp z1r>F(e0CF8*{FKel?G*H=FT;Z_6wZ)YkvG)x$XP@w12vVp=s|Awy}TB&$z%Wb%r(S zal5tc3ID@)US8z9_kqjZ=VfD3@(w$v*@qTg=XosrZTh`G!LO$UT|WJz?0<>HbtXAC zyA1Ie3m%vKFZ_2ibmof8&i@K?4gXskSlo~4Nq(j%lNl+#Uc32St@mAp&Uuov4Y^iL z&bsV3Um=l4?DLERvu^PH{nE7mrqx?BIsg{u;NGMe7B zdN*ljYUP2DEltiVPCS|3w28a<+gu5sP+^{RmB-^Y<;mTV2+* z1MG59`;;VZdW9-{xOZSpQRa!b30hJPzTsxi)z+W!ZtJVJ5hH__(A{E zTjng#iz)lJWykS}^FOwo(OR;M>7B-s*xx4$Lv>%@EoXZgcr|&NM23oWj*o5E{3@d< z=@nsN-&5CIJZHMmd=o>!$K1g5TkYF(P6Q`Dl-cR^qVY%P+|>(NA7;p-A^sP|LIUtJ+hrk zrbT|$*_(~Ga)h_64%{+rPFLyq!cQE|{AH7wXYF0m?VtEu)shZ zB^X6t=6oqC5?ZT1yDohyaS#ZG&EBw5xCuC-=PNcCzu?gqEJ>(|EG%Z155nC(kF7uBAn8yDzZ&uhOwk5*$gt z_?n*=zh5Y}?wIiXKjy}w|5>tZLt+e zW7&;wind4IeZ$*vCPn8&-wL@q0ZEDC9!p=H{9o86bV9}FLv3VzUOI=Ffd8(2I@8*k zZ=S3ZyD*LEc=P!vxvO2~rD?p3s+RcCmp|U?MBTPof1cy@5t{A463Vl5xaO` zUjMe{xu0A719{$gFTL6IJxNkK^q-N)yXk(emx9)w{cquz5w=NQT_r25r=q=IC`zxF z{qB5Aj`c}$>V@;fm-Vx0&UIT*>?^#^ZO-e%rc;irkxqOwW0v9WmfvZSVd^V|j#^Ax zIQO9_kDS-<7r7C#x4*Uj67KETt()4nqw0cG-FkJM#q$0ygg+lrJ=&HbQ6{xpnN`NN zsQy@HgI7gHac*iMbC#&b!F{67UlyO(yySKNp1I-@4QC49&$<-N<#ucyCSR{&R~2j&7c`GU;3Mp-x{md!Hw~ z8y_E7y5Wh?k?C`@{R^9=kIykw=3#x@cl3Vvv2V(^7Pwu#?i;%PZk)(m&n`jHRp;|r zFV6^)pFIBpSK-|z>2-S#ntcktdq{QeE%!wX+RsE)-^#zT-o(FER4m}hYnhMx^fcpF zE|sZJ)|(&kp^9tsk!N>;_vcIx^2qLf`EQ>{@Fa5<&p&Hs7i{MUU1qtxDS-9c?v%US zvDYUnNdAm|R^E|)>3#i%bpu;`tn2w3jO5aPTxeaI9 zm5X0I*1^IjwP&_O1xJc*(z*&6^W|G+%6M~1O}%rN%PYNP?skLze7~8GUv1?J_nzh` zU8!R-J8Rzi9c$~og>UR;^bxxD?G)n<1NErlk49O$R;=>sTJfQ(BUJxM#Pi$6K@U&= zQ2gy9_d7eO<>ss##leSf9;n#ewJ%|AIiIHWedXuj^EXdocb?m@@@C)L%6+V63(t9U zY%k)D%8I%lZ!B*X**3fM%mtn2vu{5#a^P%hP`h<^huwthAB<~0mcFRIQ*r&j*xLzd zH+D`qt~2|Py#DRYNqko>X}d+|{W(x(UmnKH$s3>9b@z4M3g2x>ai02F*(Y!QT_<|| zcTB;$9c?x-60;ZC9C~+qUea@|B^`2{+peA6KG`ESO;Yyuy8e3a2#?~_4N6<`x4B2y z1RS}vfz?JTn)ScKqh$d}V*W~Nm+t+$=+d%+5WA378!{F(@IKhm_R>6Y_lg^P&wTND z?|Jpxk_jz*y!q>PG`REl$t*y-4K3e;<$O{_WVReQSm86#qh74=vwaobI!a zoG}co{{8&Gm4%$Konj^nT3JfdU*~$P_idTBYr=2Gkc|nqreyN69{nb^j{W-mJ5QQV zP2&Do9cd`#C3-GAcuRX5GvkHZq7T-6Ja<3g%u6$-b3FwmQj8DOTyrG7)~&wA7{zQc zt>*qr?oxI)#nNSe%RFN*E;&Wneca_`c;MVJ>lky_RV%||0&%e{3knxN#;qW@V9r*O&!eA`fX}v>~rHQ zZ?{>rbz{(!XE!^WZvU5Hp7biIk0Ran-?-KZ||wUuu4vYC(gyK3I)a)e8CYpVX+ zlKlH)fYy-}627siwLcVt?`e0lGH9>4^dYICmbQl_YtLX0_{$1Suh*Dvory!3+o zvw7AhrYsegxv_QKHPv1AUlt0xi@z5AA{MzQXW|Tzn5FWTsekoeOgQ(wi+HT-8-4B%JyC4H3*b=6L%r~gyo#O9)}xmzMlU%`hvZt z+|sw(`Fw(2esb4aZ>|1t|GfAMn`hZB6uoK7mK#$UKYa=JW$Pr}%#*&ac%S}?e|goS zY)4mkhhU}YjO))IpV;WTS2kkrIkjT5hpyWfzo^lCH(NTVv*XSrq0+XD{(mQCtqxlG z`JJ;IPnhK)Ev`>i9sYB971q!ExFm}6V8-c;eLG4Q1$e2P-VvzycT&;D&(kVio>?!a zZtMNCq?7NvX6OD}9x;;FKOg7ybnH=_^`7O^LZh%pm)L5vqv~FSBu`jpdai#~Q`yQd z`aAwTnIgXYf9&O$X12VXZ-1_fMP`RaEx1>`@T6?jn(9V@Ka=Ggd-wTtlohUO{P%Hg z>+?w$md5A@`OS_~nmA?Q$FB+^$=Q*oKJ?k{zFR&;dKX{$agLNr&u(9h+Y-L_z~!I* zxw$R76)rV<%AR9h`n7Xw#7%}75jSVP|9E5T#9tFw6IP~Ms$VqxeX%+x+_;uON3SuY zexdZFnQgMg3)j7xbkd+=_O*m#^KYuVuRHd`fTRDSyxzqBUG=MykE=jC z+qJr>sc-Hz>}jZV_|V!hSv{^c`bOQi2kU0##fq~AJeE&;TG;CP;NjOTVK-jsv~6C` zl$acU=R~rdfAzy}_DfGP%=gt+s;lAqHj}+buU_nwQ=H-Zj1?CboVX$P%3^tfz+&Ag z>Ou3j<*@(P=68`|y&hIE@5mL#=30rgefmoC9_Sg^Kj60V-O;2xS=M(#^4GxYGw4f<<=vh1O;;gf=vT@pxG3}dSu^6lOCojXLk}|zJ9q(Fhi3*m;=CiE& z=C>_qNkEwYuX+Q87mrO9_SmkC{$hJ~TGjL;#n*Yio2*=Rv|aebi-cVgh8yBmrvIH# z@B2r{^mxO|jj!Xc%>L!E;DV_9k2f!_f97=DdRK0rOU>K$OTJg!VF(NAD;JF4?X*$~{#{y^ep8 zN_7lZj=iki^Q`06b+ao$?e8!A$;!HT+FHoRK}2-IYKM@+y8`?84t?5OcEj$%oM*we zE5g6m{xp5_GIeoKXlLl6*&|24`!Ci65;TliDsW8w1GHJ4s3 z$%*c*Ir-@6_ceA4G!LDMl6Wq=E9B}|o=o1ij84VTpJgBZHu0KxaK=r$E9rfk|9hO> zBYDd0(MNgliMs;anz**ianRkoz2Mq}3k*gkf_{(prS6ye?ZD5>nXx^uo>O-x@z80KQRSG3P z*~nT5{cl<^u^@Kyf6MxfUT$;pt6~arXZxF%pV6M<;b-6G7nb@~MeA4KrC(gHKN)Qn zH#kZlveS7JQe{$RE5AU*n_{zO@?_q{*I^X7RKiJ!_Pjp80Pmber#I{(>_>dtnYja-m zHuIIcU!OR9TXp||{k7NPl5Ov_ZR=U`a6ij$vz-;ogMvP~tg~*IXvmwl>-Oc%TJ4_y zqgGGs4X<0g%9-!{H^z;v4d-N7T631~>90~_ZoQLo6bPlINR{Qh@>|Bcf5YUwHO;&4 z9rDb2ylT2;b^0yK=vs@PmH{rD;j`C7yDzD&d$zpofJbYR=euQ9Q?7;R_9gx)UUpe4 zAU%6}&z;a^Yq#E;|BxZ`Ylgy%r~k{?moHLpSh?sbTlbUoVM|iJJ(D=LNBBmykVJ0y z!%Hu2E#0+SrY~rdT;ROh*G>mm`0l-YY)XHuV~ER&Eh?N7Z_GZ==U^E*bHUzM-1o0} zuk%>{;4r6Jg518w_8Y1}TONhKeAa&L(&>h^Rl6LPUTATssw zo851H=c(4dns;m76Xges{N9FEozXvjR_k{Etd5qVUyE4N-bOH7a5(zAkjJbx^siLV z;)t+Q7gldM{vn|n8%1_$e;KgWeu{gK_r@-=DU#-`fJ^xX-OB1~M`bNybuKA7g= zTYK`lxSEDomW-`J%i0AvdYl432f%N^^t~<}wlro7d?crGATyyp4 zC7FutiN-OLtBhDX+tx$}_b2_5Ee}=kG??+ZHRimc>EZ6@=k=tnPu6X^-IsL1z(&dR z4$EIYPo})B3vbw4U0MB7bHeJqJKkj+IHU7LJBG*P*4=ovQ`bE`X3d_&^FK`V?TuY= z@g~z=AOFO=Z_2?B3hfK@kDvX(yJOydTknMt`(~%#Xqm{facL>r2JV{{6^oUZthAi@ z>Q=XEY^~|LU1#3*BrOZ&^ATUXE>!gK@i*6%PwSnW@Hub$^XJUXTx;i_`Ez!je zJ!x~zzxqXIdcXI?bnkF3UH?5r$ZL5X`=he|rl#}cXX;xn-?@z4o~`p2*OD9Qt6aLo zYUCy*I9A&4Vf>Qi)V(y}tk$cv^0%KmB%ki7*)ThXd)3vpk4N|nC-6xx)i9cJLCs=y zgvRP6&$LeX?kay49({ZF-q)A9cmEWe(bCg@;FW7bMn}NcE504YM{c_BTPVw5_~UKE zkG6ivje#dm1s{F6b)xpQk9s0Wkvq6-CM53OWF-+)sM>L4YLC=v6|N(u`zHDbC@5N} zuJ)gGgYld9%_&BaQ)Y^_EuO?A@X%@f72S{j>-#>~KdQ@kp7ZDTjsjhtIw!w5idM&K z6-&0TeZ6t}!}iFBw*Cnstx472ue$jyUUSd+>bgo(dsQR%1DxBoWNv;tTV-)pw#c;5 z=U$R(ZB-k$Dsx^6i;uG0_4uP|jzL9g-OU^p&by6^1Qq(&Tf>Fbws(H$IBhy>F0bd8 zi&`(jqW%lLHpng7@!#xV)~sWjINtb%?zgYm^Z4n%-~HF+KEJWz`us@3NqLdLq))GJ z?iA(OweS$T#rkxfPzJTx=4IZi_Eqkh7FTB#-px`c6}J5Z$L@p0MI}jl1;jn4yJ+%h zt1UfR?4P&p`-YaIH97O8yl!|zC2!RZNp19Tn|bSTsQBM}=90S=ueq#c^k?c;EzCRc zn)RN*H?Ca*evjS!8=RNfy4&mCiQYSnRkmVR1BdrI z+lKY*9mY}{L;U=XmhNVmDJt}#CEsLmIlq#2o7Ssxd9$)f|I1EP{hL)8`|9M1BBtXe zXMOg^F%-zyNi670+iA~8OFZTagG^MQJ4%@u6f-fr#-mU1UIkJ6o^O+@S zFaHK;SNvJe>Clt2sXTJ$-=|jvr|&w+yC7<3xa6jJ&joc=f?4_}ooYPCeY<<{4->Pj zMaR20t0rGaJu^M?$M0>^Gi4X#$8DEndBmF0_E>rXXPKo>`k9Hweya9<&z^SATGbk$ zRgrkjW#8E#9@*&;6&0MOOvhM{+&+1AonoHif4+3Vtrv`Z}LWM95J8pFQ(ug9{uVPItuhhu+{lVdtaw`A$Xd z6lL~{EHQtQjtTg;?|&)Usr7BW%Bm!1##hSb8%-0OLtdZfv$}_c1FJ zJhNlror^g|4mbW>__(sdq-pDV<&=4!H=+--yujKiZkO?*+_1L0%By==R*5dGUH)hBdjV%2wk4>-Q8IJ-zi$->@T9OD^Vz<^daV$Y@L3!tvvSJE=}b-ZGJ({mzHk)rpEVj!>jeJU*-M9 zFKtoT*s-Nm?q&0*9rO2Hd$rwcW}|e)SC+MNxjqThebeh$#%ATk%$;S&ez>d8^{ncv zOVj`6&5eKJd)J7i=j|_P9g9E87v9P~>-%HiY?`Jur#SuYCm#3Ilf_?`ZTqm5`E@mK zZqn?oZ=&}LS2Fd7a=h00s}{2(*~lR8qCc1KN8ZI*-1?io7ko1{Vpi$tpQ4d@c7b#8 z8KnhhJMwei%WCpJlTb76Z7DJP@=mq<)mld#=S5;?_#SiKwR88}y76)S2)=7 zeb?l#4F3HRPOB7?y*)$6OWj%zTsQWj>lWJ^8a>DUC=7>V|Ac!!VcY8+b@_ONlfxN z*S@&E{Nsc2iXX``E4FdnDpwWwUetP_)AW?y-!zSTtg|+~snXhc<}OP{mYihTR$E)c ztNrJtC6@C!FF9~-sZqUJr&Xld zUdj61%yRt{75f)vjg9)r3s>_uFjW0Ce7pb2OyyXIqlMGCPkob0Ol4uJ)LEu4^}ejo zzoPR{O_OBsl?vbE@k{Qsoiu;cB=E0f?xMxUmUU|FM0g^$eLeU^)6g-YN~ZhO?{vDX7x1d z6-PcEdtniC)iF=wjfQzxi_Hv^kMAzWam0wt?z5ZG`TpX&-SU$R8~=*;IqfTYVkfnt zOKY3N{g0V9Ws9mMvayW&o%DhAcN%BuSI;G8MG+IqeE*u1d*&CWyPS9s zaLOTjp26iu0;`MzWot9%Wl!G4c$aDI%h+uKfMzp(lExdSRCc@f!Hr@r1B#?vgd zMQ262)iHyzJ1>rI|CS|V5ih3^<@USd#XEb+9s!4woB6(cFE+#GIpoX-# z?F}nKlaJ`|73R(rTr25t-FP+o+Nnv+Mc$kj*b1a>Z?wGs;e*k7=4cOvt@D3ZHk9@B z?0*RX_K^_B{ky502ZTlTNzH~*aarSSI1?e{ZH6cu&S#4OfzvwU_u z7g7H(;nbrGVJrfCOXEc@&gyr4o&PB?<@~KTr}$R-iI$c>(_SQz!^v--xxA$2t(Z$- z(5%lfp4*)*pVV1SNc?tN?8cjaA57onznYJ*|bzAlbRW16rM5T7>WH(llQ3s?psdURd1J?AeLeV(sFTc}v?~(fT zVumkM2xB-)<`Iis+pKcft1_yMb4)$f{E(jg+(C9q|M^w^&bEQ4r5=Y@vAsDp<2Tdl zPxl`OCDwKoMt)PUXpiE1yR>9a+SxhHSA0s%w7KrTbjVy$-%<8+HOJP~29D_)pPD?Y zI{2G+>BFK4fig{Z);uqp7cY5PxxVuHwat4c&8>MPrWy0oQ>Eoba-As~djx~}l=r)S zTV&X^Uw$m7eB#-)UDM-Kt0(A-y^fbYsq^}`Y3vz2`>-$G(|&Zwhi^SM)nL#2n{1yM zRQKndGSjv2%snQ}V6^+28Gi_$QwEFX+@e%nX~vy1u5$cio5BAkdbey+R=4v`9gekY zt)v^@ZOs-G;%seC(4KVi^e*uN&smDiaX*q8ro3a{nYrT5+b{8Z`_{5quJoBQafgIy z$+z{9_myiB1uxuqx$qG0(q)J4{7~jx=BBmty~N!L?g?IJ;$@O6+^rUy#HGv>zTjy0 z)@q`}&ly6dj&^))tGBC95IH@uXoX21*KzN_FX~)dU61|U`ufSIlaF&_qc;@AL|^jU zxXa3f?P6KNg+oRg3xn9z&YbmEUK04~XF^_Y0$=BG9|gh4v~4GsMks0Lr0iSPetPA% z%p1!jg;#u=H1Xk%_`l`5XLh7a&OEq;->LZ~gV?GW8}rXJ-Q9gpIEd{*mHC`R)0b0U zPgjr0S#Ks3wrL8lNJYgx&${nh{q+?NBswizwCVn7?RS4G4;Fk zDZUC)bI)$RP1E2b6R>}l0WdB<|?XicW*$(4*rn$Gsg zZ@x8dYux$S>*&_*)4OaMBUP8@rhm=mW@lOMcv>U)Z|IBvbtel_4X^V4u$(XdK~eB# zv`B~N@7x34Q_ipmXlk*}3hutKIAdKoxc<8@;x?Am;9%zbaKVDs)I7^4|Igj7M_@TS$orxD6^Ln48G`G5b5f^{>5GHZu~#< zXBk{~-uN`~PTe?3?f868=eTW4!o6?#^lWeTbhBz^Y26T7^R_DK&7Dm@GI!tGWb?^y zPLPPxKc>fvO_KIpnQyHA?4V!ev6!9zo3(5YFBM~1u2izdB~ADJ{--fM&sDu1EZM}* z%GaZP-F!uq<&u5DlBHa(yH|;5 zUeeRx+#7Q{j8$+&@W$I=(vJ_OPG1~b?{f6~jgQv<=KinpIWBKQvOJBcBkF=$}YVVQ$>&w;!7&KnXs`s4O-`K*||k(}`*q3$Mq1d8YLvt0P(S zFk}1oOSNI$hc9ast~Q8FSotra^@Zd_85J8gKf|^+hJCu0$2QwXr(1c5PrV-_FOuB4 zEYs`k{<{6^EP6a1Y%F_mXnN6152Y`U)O2O}?my&+~tIr`_WJX12I=ivO*g zW5qIT%MGV))cbDCu5M&vd`n+RKUrax;y-RN`zbHP*CnLovlp!p`_5fpHv3FoaZYo? zoC8~uj=mPJ_VE7IF4Nj$lbCAk-EY*;vFn^gLc*U-u9qvK;wHCzD$nrWU2T`8VsEfG zG;_VorPv?aE^2X|s+oWAq?X4n>*+5peY2i-WWUyqu$6g_wzaltJU?e2DXH;clT62} z+1r&`4@Kf9RDfvxY4y)-)V83@PZOC z(Rx|IMbp|&`!M(_zDt^8CwK0R;DK{%`O4_wZ8EPCaMN)kuFVNJDJQ@Xjg@!pcZIqBiIr`+Y4%kFV$PHfhA*MSjza6xj(qWeI_Syn=H;p-7CL9UV4!DVInsC%fB=&XS{EgwZ%IW`9 z>)9GtC&hl$3XfjD`q`$tqU{E)52n^m+N9UduxeX^i@2&V=M^)?M<$oLiptNN*|BZw zoP?cMM4Jre_P+hLe8z%f6F*_7cZ$`>OfQrQLqYX>yeF z(1mNygDO6ywtDJm@2KB0>BfpVK?2^-F6lAzPEuTPV$-p?^R8A(|9%$y`h}Z#$KI4h z*XKRj{AHfk^CJ`7t)0pQEq`iUE=y-=zP9txo+yUK@Q|K=B@1-?R``nr9}DqNb$Jx} zrL^znEL9_?{=;JT=OyR=uU~heB6HV)=(yjyu7~#tPkAT5YTxxUHT%EZ>1>?!C*E9O z)$s>6!mM}SwP3bhW14;PMl)k+;`F?heb+?AKFIy9Gs@j3?7{G10smIpxNjBpt`j|O z-8nG9C@YNLLE^!_e~ML{=L)={&#&(E+cS55>CJ;o3x0k7yZT;&Lqv;OtG1Jej@FgO z71~0|=O_BJ#J(`Soqy)WnYSklI*dO|vv_JQAG-IlV&e?|zt)0$kIqif<*5%{GT)l} zYulB7SJU5?mS&!G;&?6>5&SZk=kwvy^`Y50JJ!anxOCx)J>P{Fmv8Qj7Vcr+@r~!f zHqNu_v)*#d{*!U$^!<%t+@itdEBF`>p6^olt+d|q%*w}W=I(wd^QkuHyM5KWKO1jl zuAkAjs7j$@!^O3A=MPNdt(xAxK%lh4KGdj>sWYpr<+?=d+{-+@O7?NJAs2hH^7C6C zo))xd-S<9ySH#9GTjt2^(%t0M{Og91pjG$>lfpmB--Z9kY6^HPHpxBY`uFnLcfy7$ zXWMsfxp)6v$Ak%Oi`N}Z|9jbRjuQXwuTlvs7a!REC}zoCb*JllrfPFHEc-24D|Pwm zsaZnVt6ZFfr2M%}18#BMZub)Zvt_OHrTK2Zs@ES4SeakAa@TjSLf#46x0$Tuw*9Ld zsU=fiP+zCM@Pe&v*^9Q5sh`z(xT4=xIJC8y?>FV?SoQ5%K~3|vyWd_JN$=Pbc>0FS ztB)dG`pZ7$-nVB+RN;8GG_`x3(0vyZ*|Y6U=G~nW4Z_7cn?1vqIlY>pCds$zWdGrl zGuN60=l%2U-M=zWX}i(V=X*+yPUx=@j@FrI-PKs0yl3Xp=auq5pWp6IfBZS9w0nnYFH z%q>)?(Vx9rt9-hO_61&%YcmA3WR{nz1WWMPNXsetY!6agA@@H#*HX=9lk$#(6Pzp+ zT~`}y&8dukwuq^HU(7E#){@oC0^65l_r;0`YA#u^-#6^1b9vdjr7!f(`>oj2QTgD{ z_Ippi%zE~r^^H^ggA^+Z_DyY?8>WOS$TW9}KW}-lewFRq{|#&Y3P@aaKZQUm6^*wArIKtLvg)jF#jXQ~x(>A8+u_=a}iw zrPf+GGs+>d!S%FDZ1bX%Ya_bZ=gxFq-f*z!%#zh@Nprsa5{Y47cr*Wp^{f>R4@$-E zT6ay|aqXb8(%)P88$Zt1PVhecH)jVcejc_$Zi&0p$~RsT5pK=JucdeJf##`!CD z)kt+;GgEuXxS;*fakZm96T;l*O>q3EsIR1|s@vSK`}yKP0mYa8inpI!Y-R0sx_n-J z_u2_!vNmF~6SDssI|MGfKY#U+&)<_LpW3!F)I&Z`kF7lUvecq2DxMOx%@Hb=pYA8V ze6#n7tJ6|v?(T!n&nzz9Qyo6Gb{& zG8=3o*+SX(J@%Yz`P`UmZu+6)v)N~>ihG9~-K2ED==d3Rp_L1^_i_}U4t3q0H>b+` zmQF^GptqShTblKR50B?(N!? zROdMk1@|KEm(?2g-;iPC`}g?cqI~;a#TKat+K=@2-WBA> znpVljoIe^@G=AoqduE#>^OZ~WsVz?zTseDbf!EvfipC2+s|%bCca6DNq*zv6Xw*LK zeUEEtgU))*|FK8+ehmN9|0UeK=3;d3^c#ELteqa>vB0(b#o=i;->!VWX)9ywhW80V z0k4@=r%ib+l;1ETmSs!Up>Brqm`S?#9Zrd~FisBHGj)Z|@(R_?0~<2xwThYDd7tEK zUX^c;51qu=bkAYJn#k|6%mLh=I%lsxVzFx#yK5EmnaSst`^W56=iTyd+4ZUW-{j4; z-3~Nf;ImY#kVnYm^Hr@T=wG8s~L{JmYR6v_kNsZD zR$*K#z|F;bvqVm!huugrEpYP3=!UAEvV%S!h=VHy#Y24j1-%aJVE?g^_Q039t_iswJ|7Tymb62@#QJGD!1xUkq+#pE>%ms7tfMR$zU0?w+5E zUf*If5@(yRZ?4|Ao_|wii_1ceT#sTacS_=(>K(R~C$Ko7WK;P&3zZkLn|=DEy`J+I zcJ`Myb3WPV(3c_B>7b+g`C()im;J{L5`4?IYfV|h$e7jI#oKmM!bR+e)vcdJGGYs- zW(6=DUvum2k5cLBGMjr31XxJ0xzv`6oc52ZwbNa8bCwLNRFq%xwcsxkuLyqMBlqOC zw0<1>x(;p2vOBZ)?lFC}#yRKA^o~a>bL2H05>wg4dN-Zk@a)Y;jT_8szR!E){@0Q5 zeJ$H8!WRbDSvCuy9*jjt-_8Z-*a}@3@ zKN9upFiUX!9kqX+8gs4(O9v!Jyxsm!a?{j}D|i_^zL?eA^xZip>ctzIvlHu=eJPkQ zb#edKlhaoA3IBT}8NsKUqqxFN+bURk`8?^`Z&p@4Jk!<`_U!ob^S`&PQla5tUhBJi zw^Wqee7xwqV*3BOBU|%4{~hA~Z+9%mK>xy@HR8{uO1&)u^VXW}O?f|QPSm_A-(P=) z-hPZ$bJ*y4*YMYd?3QIO-o<@du=k4Q1GSAQ;`~>b-nOj!P!_9mqoz?D25oQx= zx>o&IBxte0HLsj!|Gai(@6dVs-m?68-^s!F`IMZtx$u<@XSAM|FY9*|eyRK+Qt4&p zHZdmiqqp2LLf){6`X!u9^bSq9zoI*=Xv(7Uy^gU~zfV;4GQP-&-D#vRZ^@jo^Js`< zOTS0b*)`SP)m{yDYO5D{sT*gQnRdDy$b7p_*=BqGiJ(^$A>)=KcPlTlrM){Fg6_9fV)qPh+fEq`mf# z+t>QD&-Okj-(b}Br)%xcefm}!`_!GcHgS8%UTTb&oPIZd=?At(-eXJZu53T9)vX+? zwR5tC{dOteZMm{X7gv~0x@gL1Gw~V6Rnvl=>AGGfO4+OW4_hpM>*JWP_~4t!I44fu z%fbuIwoaA2C}ia9(6Wb_Q$k0TjRt9 zxc5K&b3(0j(G&yuYuYh$@;cuAn$Y%(ZAS9uzg-rt@86~UY>N|l@Zp9W*D_J{l~Rj8 zpI(09u=bC6&n$1XX3MQ9Ty)%lv9mJZ#e;OM-)`OQGI;_GTdvQmUD2;MrFv>>+#^j3 z!_)8FBq}}~{eJ(NN$boXCV|DZj zxAgp*74^?NkH?$;dSVrEDI@h}p_Biaj~_CnFV$Q+Hs#&F{uQ&<=Ls>%i7uTv-)Pg< znyfSXWkN&NPI&4a7x8%JtJyE|`rXfKUix)@dTIK!Q$6qAhp4i*NO1+6(z&gb*dLT^+j+;9??jwjlrPZ={MDiq%)=Os& ztlh%!KQ=O4qH2vo#<%}r@o`^Dw*L0e<=DbtdFSB0o2&nN^={qT!fM7}_({jue}?RX z?+uS1uCinE`m@^MdQ#n{dvAl*98%eK_`%=MYX-WzL{71}pA51rogY#^LI3&ZrnZ$& z4qo#+5VqpVtQDN9*8_BJo$$R>xMxe_s*P{YPYUw;SY#Go`LlbQ;m7IEr#!n%G^M_s{ATLQ`VymMUA`%Yb%8#TXyO*xY@Y}fzh-xvNK3{1HEbH8SaYsd4KWe*kegd@~%Z9fwBJG10) zQw|T0?RsUWx|%BjVe!{*iX~`GSrdO+N%%-V&#KCbTkC%7oxb@rCB1!b&VPv>g$+JJ z3^oUM1{qifyjb^RUvg090Wp^sJ#rmye>hEF(e$=zQl{K5#WCvDS0*Hxkk-0RSEam)=w4&EZsO~xHFkHN|GDXWxaej_?hh4%RkoppL0Nlx-{jPW)~)#YGx5lh-3n8+pJJCXNh0>tw}mY zANRHVafu6F{Uj^+nf|9z>nD-3r<^yGpV{zYRnt@U+4)Y<#lreAP3xO9HQiJV*4KiYf$-N7AIBRVluzcT(i zXa0iA9`|gSB&^LCjtd_=aDKlBzhKTz^Ly9z9PS35I6XV`O5`pHGrMW=Ph8)|EbqKv zx-d58*%!vCYUKwA}@fJFA1Y91d_PIcEQ_U&x6)z4+Pn3)jwyUwx~i!B?`-P^MY+_Br2u zW_OK_eZC&%oA_xP&pibOf1OQ%7j8`r5O+Fobz#SplF3JM6jhh}EZJ#O`LyKs)MBo~ z%nR?@8Z~u%3t153U21m4x;wgHG4sU>8~0q0c~f=#@ZkvgKhZvq)J|nenD(#tG)S0y z^>}$x#V_{+*2o1bxIRAraU@~UtuyD_e0^3=wUK-I^pW@0pjF=g>pwFcNcvfD<&xh0 zdv=Su&o^J@^eRcODRbCV%I$P!`45TRX7E`e0RGul2@#@m?;v9jXiII#&3bJLF z)Qa9jXP7*5nQm6v)E6J8(#ga1s7Jp2u&GCt{(r7FC9|KLW#8v!>%aM#%uLg&qsJyF z%YJA&yYO4ut)qcQmz+D z$A8~ku;k0l1AN|f^DNbL)~KGA%g%9lB%Jux{=B%Vqnp>N@Aw&IcJJScXt6auGM{aE9%F}q~G~r$1dqh{+0L=xX(St_vnr@%Qsv72@m*t{~CAtWr+xP zvyEqM8yrIW-U;74EWvpyK={Un)wK@uZ+|$lBZPmug!4LubzXIA-}O!Jc<^`WyDxqG zeruVizH-y3EvApg~75lSU5l5E)Id-t~ys&Te%DC&>EecsKd`bPi<}AN->k`!! z`nez0%xVhr+7nVKng7pBHu36_m)tD=MWRAGx(sh061AGYbFF&WI#n&T{K9?K3wE&B z?q54U(|2Xw#Ut`pN(8y=wf0_-Uh-e#<0aO(b;;|U&Si&QtY0Z3C1ei zW^C-H#R4}=X72oW*W$x<>lw8-9CX^PB(+#qT=e0obnf<9?8JT7?Rn;74NH&UEsQ%t z?*vYAi1}Z&KF(QO_Z_#W>V&?D^;fUn_0U}!xcb;}O~!LmKTpxBl@D%ve(i;Wp7F}k ztiV}MRs>&tA#ahP{wAy9{fhTF)hB&~B9rYzW^{V9^13xFf6dD8AJYAX`PJvXfQuce zMdfkbg1;+bvJ7SFZc@o3Sr)Lf$=lrs0yx z9&>&{pguz!m#@{o2`l~2CLBpBm~X%=J71&v*3)Z-X?DAPBDK=(ezeI;`mVftJo4Yp zCf2g1!(Rm-8nOB$E3FDJbKd9j;lz4=D{cGaFK3LOvb_78Xr0;8c=gZC#2Zl@W(^%p z8`gg~Xf|)lOO-jZgQhQ&&`3QhV8rgO|7uV75xoqjj{$w(CY@PeTWQ%Bf2016m0}*_ zJr@Ik7dJ)ke$FU6Jt|1+T`6*|X>Flwl5c8O&#)!F%FPk8&+Tb@7G)+u|;+rZhc z?L5Wso#FQ_(%bIVO<&hF>%jRp8qNPcNgUcU=h#Kvds15NT2s8QtgPIpeZ@_8MYXH8 z+UHmA)_f0X6tz|N(kL!@s!=k>WZNfhi;!v`pTA!g;gkx~h1uUw0XAZnvJFL&EyCDo^{4ZJhC(D_iAD@}VOkMN4kYky!ov z)yr_!x>c}0>&Ys>MGSxRETDJ2tWS#G|ReXQ6O>NQKYOE&Lmw`o#$IQLTU)QPmj7-RcK_XU1+pSY#EEOgS>-na9= zy|rA|@a=kQa^WI%J&U5MXd8af9Y-&y-Cz4ID!p*Vi|;23l&fAT-g~US-s&;iZPhpX zWuJ7M{jy4Yd-Vn5SQ%^elHMaX_b@%rOg+2r-D(bz=34J9Q@X7e?O*a}vD3~t28G`f zKlyHK*|$xqY(--D?}C1gxRVR)<{Vq#Wpl2){evJ&Tc^v5W84L8!E>&1O#j@%c5iCy z?zvrVJJwE;GI)IK(n25_8lJUO}~VkIM&Hb{`AC_;fPAqQ_X!l#D4IvlH_;n za&=-%(tLaFccxZ+!`*{v@9p*6|2HhTma9C^>!Ev|^XBA>+FDNbnmYv#y}tQ+-`tI< z|E2F2fBP)E;@`5uS35Z;&W_tVS0~N%?7VuJNv^#voExm(bQ`Syt+TM_=JjoR6S$^F z1}ey%y3^75Nz&n?qV*B^^|itK9-Nsj&VR|%`_9n|PZqQ7o4>l-^Iy`M1q>=TA}%Z~ z{PBmf>6T&hHB5ft15TQeQ&E% zEy0#qDI&bZ=?t@1`;nR zF}l?) z5<=7>G-R^E)A|hC)T95BJtqZz0}Ohj}ptDU+z87zw)g9e2t)Y`(0-Ly0X5f zblvI~5tS>>KkKx4tZ07!jr=lq#pCy*jpWS=F3en!mcb%%%b4Zg&aSo2$7)LCzD?xh z(>Qg@T}=7!wgsyS%mV%w)hs@}p5fKQh)Fx+Kl=ZbdOYiuh3rn=`L$L58g8<+^uOQB zY59dwYT98LA%==Kiv)hnzqP0;vAW5!XrJKm+fK*r{lD3N=uc-};@ed8QkYNn{fk>G z64g|us~F_>SlC{9m#UX#{UucX(W&@%rj>nh>WxixPc$WNwDw51vVKi@a4+Fmh*?q3 zuLs}0UA*!xQO50|g2BS*I_Lg|H$VH^w%zxegs)}N5xtWfZF&Xgrz@~O zo4J;K>4bAv_p-M->@Qe!V6pF|k0GheW*R4c%P>hUt14MoxMj7qFi)lKw~v2+eQh(3 z=9#|Vo$2eNyG_3oy=#4UOMkiF`h)dQ)b2D+3IAU&)~RQtZ{=QkA@kPn3Aaxy7P7Q# z&ATt5^~^fu;#IDP7czuxWq0g#$dT9WO!A-qYe8}4*N?}Jb4*q*cI$2a^tOTR&Ex3; z+J38EmEJzt-tkgo)6rm_S9@Fx*Ib(U_oYf_*}eI?o4Y(`$uWyVUZCN+ZKi zna)ZFJFdf*G8?!KS8^S-^Sl?R_rot>qHJ)bP%xkP`5o!Gyr$PQg@V5|pVDhM#`b86 z$IU4jZeO(@?pmWM{5M6U@>N66*Y)4e7pfiFwczPv9qDV~H(J(nzg!(Q^=rwd>ooz> zP5&I7a_W$y`70gMC68l19NgJ?Rd8eT{Pm1%@Y z+0>Vj%O0)064}V`Hrl&Ae%;Mern5qFb5nnowmrZ6=!a2z>pz|EIp#~ncxNBvu@{}% zv3+9dmW91K8#z`S+;uj$w=hET`kDulo^Qo>+>)MD8X>`@x#rrZ&vO(4QcKV8tXp)$ zN5%i>MK-PHA3m^ofAsg^?=HVOt*d?ed?}HoZ_I_4$Vv-aoS0Kit3naQ)9eRt*27 zZvHXf{3D0YzU^&2ugyQ{seeo}KCfC~9JFrUY`K>09LA?ir5qnAmc{?sH6=AL<#@M* z8>4C3wkZ;+r$VJZUYaWwc6GXHjP}aE?-|%9Sxvd$DYW!kki(2kidD}X*}cpjPI)vT zr}O6Gdm*>(uWw1Ib(0ma)j#x-d&$SVw@a-eg(m;odsn_cnLDuFTE~5<)E}F|+>hyB z95&B4E*EHhxT+*HUEFJ{R$yYmY{P;szHO{~=lgtJW7OD^dMAIvxl@mfYT2T*PL|D& z*fnq3tk^S*vW4!Ynl1}9_uHGaKRs%$JEi?-(W(z_i~p*ezWBGNL`~6sS7LROKJ5~JDl2=yiaU8; zB^%|eB`v0`DG*Rw(fhn9CDZl4bDK%uFI9ovGT)qf4SZR)qFC9MPYWmx1{nv=&7glmqE!uV8_}aOZ`;NOOKD(XU6!J-)Rr438 zj##MN3WfbQil-mrm_JQzM{V@WU|osLU;gCFEz8PJ-(XzmOFv zW2d;*V%}sIC9l7K4mAtp>~f0we3fZp#P=z3>MP#Jsn43R?i|PSS~*Mo-i2kM|E;=% zclI5ez1ApzE4I_WvFEn~;}hNI$3$-0yokwuFmG?uccHKfgL>bF^j-PqR{dV~ySKyB zR;BaMRl#F65nJUVTPwqFrtdawT|fQTi91{CqEnCBGrZ-<^6$5NHH(Yu+0-peEmeN6 zlN9o_E%I7VDb38gKH=A6S4qohnQ{dm&n4Y%pK{P}_xV-bJ`JnvTiQi-^``&g@SIps zbLFk)L6HpM+?}&;^7GHxx5VjTbn5HJ6Zv*=ZCk$N(uYq=jdC4({a=+X{It3=z)Ep@ z-Na+3ziJ+>WNQ2y+wUfE+SGh<@XW=_H0lgqeDvha-K`w=&TKB{vY_eFtU9@(i*_9m z4Z7V}Sp9j19QSqpBj(PjO&=!;PtnyoHf8GC&Bm)Z?1WnOT{~}oIdSvL)=&4G-|t;q7r&wImc!jkW{Z}7$W^>Q z$?Qzn()s5;3d@L3zSbqOT_fXV$Gbgu&FwtT7e_eVUTDM}yrrwU{cXG#V_ILbF6Ja&_+cx< zZFu??Q(lqeWDP-o-s5Xu_m*F={UG%%OE)-yB{20-3v5C1Z=`s7? z&O4#Ey{>JD_`QVhW_#%USf!~6*G_dV;z{K{q*B3nlIK}Y<&l7IE9C8s@04YIdw0@$ zd)cYrINmjztU^U))^0kpz~Ep~XA{rDD@rwsfAWON^9p9>&azn~vF=(LW9uuinSX?` z_BOq~w_t6_f!k@V+mBSF*YYaQb2PH)SEvsuDqS1*=KSJ^-}q*`sr#Sm?&G++JL`Yo zF~xqHd0)@!^}XfniQN3`a7Spu$M{`BBG>0X*xAEm*dZTb>$2x=qC)k%J0I^qe--v~ zeb9WS9ht}UzOx$|>g`+AdGPC!gKqslcPhL*^L56&zpt*{G<~ANP?mT4(>DFZ3pI0l z;xi|{ezoetr;eW-OYfc$HgcMF@n~#sTuMNOZ};OJVyXQ7b~!7T$W&HYbGt6&(5~Q2 zx->JcP{_uuFW7SP#4LzZM^9E*xNM;lV;xwJ*l%alj-8^zQUP8=I|>+h&}u#bJ0=oNLQc%ND2oKDNLGaT9%y|=>)yC^@|n~X4eZ_f>Zdeskmqo9}PMXtp?+i;Kmk2o_D*S6a<|)xE56^WwiV^LXbk z&UBvlE_M4+gIj+;J)Ctr)28Lc@(=eq|0%P*m{A(>a@F4}zcYm$?;Z8t<&Q^W-ycj^5vKGvQCSX!A~~(_i*JS$X`~(rl@$-Tw*;W(0oE&eGb# zZ`!-=@9czRE%C=6Bj(f3pB+I_L1V*0u zcer7eUh-2%izR=&|0r8AwGLIHLlt_)( zbIUo$~$XobJ(2MpO%=V zzFvEAGpB`b%{Ngd%NdQF^X9sJW#m7(z-EKq)T@7f-ZtEma?L}=f&YwX-r|Jxl@Ti* z3eRG^`{k@&3E?;<_H%x1e+SlpgGYZddp>J|5nPswmBWtlfQq<^c_FK@1E({8L& zHEKT-^J?2pr>ta+txAPES4Pj=7bko9!GE12_LHZ5UGh~-{%%{b{Qu_9m+XUYC%^yL zf6UK3?4$U}`}%S&S3R6Q#rSb6&5Qc+J80#k%W}DDuJ2mzZCLI2>|R@=Pj=i)*Mja< zZ?3W5nB-fvB*a_kN6D0aw-XlXf`5%I_jp(fSS?-LR=1!-V^_A3-!|b!(U^Sc(iJ-_ zQg8iv#YC&CXDf0Wsj_(6Q}QU^x1Ov{(QK3qB9?CYtKEW7*h)zzjI z?{66uv)Wa8UfGae>{q&FdvW+AG10SfPan1i`u+Lkoom^svDLo3f%SG~!nNVo$>vT)JTE;Usda|-?7hhRs zlcBpatL*inueZ%=mbVECB+qPgaNeZO?fWODf6vJ+pEU2KWiF4Ic2jEgJ!|Qz2|ktn z(&x*r8m#nPq7nIK_T#5wS<{zqO{qVubnfHM)v^UkzegL+K2x43;qGkvOYiuGHp>~` znd;}Cy)Mf1&pk+?=7)j8(W=vF68`K>&VQ;ZWBH}nR`MLWA9GYRRLtng))Pj@3QuaB z=rlhbpxYZP{af@#LHp#Sr5P@3Qg>%wa~DlJ@?I+U`1{G>T&=6z_f31n@#6Q%O^@&A ztJ*pE8-2H5)Ee-`XP#O?mhtMnpI_9+ZAyx^`tP2_vvN*jnB>e&Uthja7g31_Rp?~% z=H+MTU|qRGY*C`IU>|2o7?;k>N3_p3I9Z9e~)DTB&=WAc1p$d+ez1l-43B;J&$Z0RKI>JiV%9ZbY|0?3%(w_ zM<2YOJoUwPUGs~Y&6kDS$K5otp8LP^gWT>^c1iv+o9IdpyYul4x$Gwa}lfKAD0;nM-Q!XKl<&Q>I$a0^{4tKICZ|3Wjp?t^O4vgJEmFLJM}xP zW_7l>Fh334ZS_U;&qgC(t|hP2eI`xvSGwN#E=HsKdh)_u%l((%tq|Dz%58=7k3Wka za2#P(s4Lsnz`;Gsr22M;-8^M)6D*w1CB5*~*{I0-H0j@- zt1HuI^6|^eIlOamg4G?p$llCNr=KgmmORXFJ)_;{>J3rLM}=mt$(!vbz7{O+Px*7J z=u<4C#XVlm=B{g>%K|^l_gfP2{?9WRm6WGdtIpX^*(4jWr0DJ4Hy1A)(B&5ml-=^F zk7;5+%yaqJ7qga6G0b9B86lRF+*xy3pG1D|5-R@J2`4w5a=6r>hW4Q%^J`pOLXho#n71&D*8;OioGaM7f=U>utif zC2g+1Vx#)~y>FJBe3tI8dn0#GyYuQB-p3Wb zPB3DMNls{JI(zwxft~d;C8^*WwzJDOpFJHecQ|LsEr$nb@^4KJuk<*5^n#b>OrzKN zH(d@rQF(QF^VR3qokZ{L%oY43+r_*wqp9F_{p*)$AzR*tW!T6staHk@ddDg=f4N4O z9Bc53S1IQAn=81Y99nhde&l#2ifbquS|nJRpPi7sG%~0Bj_wtP$~$T)@dXpaO4Y+v z1*bm|T>n^c!`z$E89gq-pFZ`kD&-3+X58@gI#wiy$*S#P^Ub>q9Nv7?c>E`9!>gSfoiZzq{kMMBxnuvF z+Ydi)eO!Ai;#1OFw)tCPE7Q5zm#zM`TWpSgg6OsbJ3crq5Z+&!8Dc%Fxx3cx?T?Oi zw^h%ju>bk_%y8o4m%`#!JUfmtNR%3Hm5h45>&Ls~>+{k!%=PZ8;ab6RdcXGepb&1mlUAboO zdp`GE%0El{>udfQ%uoB({{C~E;o_NhJyq=&GjI9lwB*?|otCNf>HFu*{-S?Y+(b|; zLaaeN^~03b@XH};IxC${KPjnWvNiO#VC&n_)+rL~it({$CQEV)uQ6H|8j-Xd1Qj{tMb*_ta+Y_WZmx|C1OO zd~K8!=c`=bE@^jEGUsUUmW<8L76!7v5w@`K zf5H4Kg{(IYvfE5r81DU_UxUYb-*KMW4_))iI*&g4KG(h0)#RzJ)<%X2s~I^p76flB zYmC+L0G(fQ)!O0870+lRtF z?GJ68CUe^8@*QXU$IWgl8jj^FZ)mZLC{5Y-fvxfKcOQn9RWLK9rtmY?Htp zQZws&Cn(oB)&;DyFhUv)5ljxxraQJw~|MpfFcV zy17(LtwmEuN=B^AFsV{ZvBNJ>Otno~Sx!qd%}RQbrok+21*JA=6*)!u6kQS5?D!T@ zHTgDaC1pv=I1vqTlSSSVs`72p%1Y8sM$&513GFh7J6NW~E}=Qt$cJ ze8rOt6`Q0S3b}2Riv1%)g5p|r?eiry8oeZy%k5Rv!bB?cGgHN7DsocA)v7hLrDer) z4WjeK6>B}kRcob{qW#WW<0 zrnpI|%GXLOE6La!$f!z3)yOC-DAvIJZ=(zHf4!`lQnNCvl!CI7vNB_+Pp2=Jx4*Th zB*fQ^65NuKQueY+3JQvf3JOh%^>T`~k}^`V($ccB(o#~=QmPuZ3RKbw?28R0j zx|Z5Dn$~J^MwW`Q=6c3Pa{4MZ>J~~;#-<7iCeo^UQY!K`8d~Z)IwlGl`qB!<>e33f zD)RE0(uN9#+S0N{y3z`Ea*8stGWrVI`qJ_ysxk_8((-K?c-OIFM?RMS+SqQRlAtz^GITTW4a ziUNnclBCsq9eE{%$%>px3M$f83m_aG1vzCY2uD#-p;eJVp-rA&PDx2!#&Us# zlCq*wvm&FiT9c}knwq+rI$yQ7{sJ921$jj|Sw(pT1yEGTii(R%NQ#SziwMcbtdBNa zpsk>-rmUr{psuE-p`t9~S<#SJpPrVSQBXQ{;o?BUB}$=-dZw0nNP5lan_VuWs+_QF z(du=}maMFgDVea;$!LjU#PZqG)FoAqS5}$XAfu!#>9nlZP)<=<+G?3xY__DVcXM@(nCC(!IvZm9Qw913z zmnuYh2y2N3q{%C)&S;cVu3eESrIM#0uQh2=1=xTk%Dz^T%F2?$%F6PST4mH)SENd+ zD9CH}FAJAYQIfV=q;6m(r6Q-QF0Ufp*`^>Bzph+XNlDgn?kp=AC1q)=1=`A>{4Xyr zr!G?7Ag-b{agK$Qx~Si(7F9)1^v>6jlW$OzRZ@~y7pbfj(Gv6Dm@1_v-?%bUN<~fu z9OKFgjf&FpYT|8$Vya4Vh0DyvZ0F9khe^vRDl{ufDo?hPRFPAbt(ha^xU5S{UP&IJ zS4p8kQAS=(%(GWkML|(fS-QEoV11^vih=^Nyt-I*k%+pylA?mLOwF=6E|N&{O^Q}> zT_&I+O-WuwBy&Z!tg?atb{74q*s=XTs7BB8d<(kQCh)CSwTq=BGt9F*Xj6c{DtTNGH7WaQ=K8x=U@l_f0~=qsrzH>ohFx2iBJtE;Luscl5Q+6EkzWCQtP?n4uIfwXrkTMar$Gxi42nMJZy&lsWTeOq*2_n$+s>y-m6$AQY%%>XjPYm%{kSnjO(pxI4q%37Wy-^>OBP?dh zg(peMxm6SuiCfLcRg&^t7%!u&C~Mq3wF;D_EN04wxQJ?sdB@2ss&<#jDVNMjgk-6< zDS2Q6rYm`xODQW$i7G3}w^hojRn3f*0%fU|>4B2UN-`GH)b!1zRpeFF_B}%J+ya$R~g+@h5c~yz(ba53$xs2(i;@18BHXtjdEv9J8w<=01 zw_8i8$Scbh^~=~zuhRr&CumXuWhYf}*G5QolBuZ3n4bX7PDt`<;zb!^Y9J>n%M?xT zbC3dMC#ZarqLqBTAv8OQCCyBRWGARBC_71dg+sHGOz7->V;OLEg3323O4&nlld`mZ zQ&loFH-Tj36y=)~7^GT}Gn1706kVjuqz21O){|5qnMtxuib1kPid|Ax#Av#cmCaOZ zW-D7WKZfX;Va(y7k zR?SteL7rVsUS3XKPF}uIu3es2PSQ$M#Z^vDMpjlvMy^SgSx!z?x=E%{+DyU3Rz=NK zPC-RUQCUeA)FxDvlhrmeGc_|YGB!1{u(LE*QFW6xu(Px=)|AnD<%Sp;h8|%r)D;Sw8%P2_cSnJ8j$;!*9yUNMQHp_|0OPlIRE6Au>YRSsUTUuz! z%7Z1_z<>cgL^^DA{K>9%rb#0Ynm6tKomX>dnW0RLPuryYdh3aaRW0sXSlW&n@l{PWg zl7Si4EXOV@FRiOBEic;u(qe39tRUM6v8YXsLA_ayO`2`VVdAl)X*Ak`|% zEGMnzIzfR!u~mUtQBKokrWC8RxUz?~?sOd{T`l7ra3hq3W z1;sHiFfcG}+P>?@PS#ypR;^n&|LX#_h4WWVSvx!ZTO50A^5mA)3ksD#D{&~Q$eOS1 zT)m*+iz0`DvW)4f4g`DkU}wKqWK>XSfH)lN*msIdN-8SIB5xJ>l@z3#oi|0%YlT}iJn(W_e~%zqdiYK|w-MO-@WTarffID;F)8H#c7euCZCMMQNHMmy)8Ak|GEzDRn5a zD5)qZDE28as5YoEDycT9v8yPnDyyogDmN&Ji|G|C-n?+$;)Sy&&7PBj@J6#D$i^;( zRz(g4d3gnS5YZsd45Hc;*_7m^71dQ08Wfwru2W!ARFr5C)0CGG6B88^m6j8ilN1$` zS5Q=lS-oY+^l8&)&zd=JUVj3z@f^zPDynK~sw&D2D(uQC%E~G#%F3;Z0xGH?*Qu$3 zBvsT@Rg{_)-4v8%4W}S#zc>Ts(Kq%;|Hc&zV1SW>sBD-L#^Zw4AK0p1$T8 zZOJuL=FFeFefhL`^XJX#ojHGgTW2I9&KecD6qIB{Bqc;e#ieE1q#0%8S``JQC8eY# zq{YOgCB#G}#U!QVm6{aY6qMxjXU|@|bjkANOBb(MvT)(#70Z{eTeWP(#?@<9tyr;g z>-r6w7A@GceDQ`YYd0)kICb*Eg|ik;m@;GX=MFm)R1Vq_KdOPUKDJe85Rw>DATc=H$w|LR?`4gL` z^-WzicjnAFQz!RMnlWu!=aSho=gyzEVBW;3v**p5HGNi7_q;iCXU$!@Y}vvkU1o?N zR#a3_R8U*IW6h==TehrSw{GpaHS1TeU$J)M?jlK51qBtENxRo=UbA8Kwk_*ct=_s} z+uBVfk}C4eij_(Va`Lv#)2GjyRJVHRyd~3nC(oKUt#9)3h08iSm(QC&d-{}Ra~3RF z(A&^Ed%>L5)8jtJR6$*CNK#i+l2a2++dOUl>aiR$56-)_vaM zc@w(lFI_r66Vy?IW`0E_c~wd071QU;ZCJ>=c+Tv(3ud*%X~@Vct0*@riYdu!h$XI^ zJ#)gu1#>6OTm-TtUrt$}O;JfnPF13K#nkx=XU&*0Z`SO&^JYw+H+R9j*>mPDo;`O; z?~0Bf*#;$3Wo1PL6-m#@3#QLrG-v*lC|PBBSjnXb8Y6L8J!RI68FOY$n?7aQ?A~l` zSrtVEd4*<05d}rn-r2KyXDpdIWzMWQ(6=|DgGZ)NX zIB)W#r3>dxX}6Y9R%llgkynv$Uov}2$AY;trp=nSWaYG6IVFX5MR|Eusg^bUGZxOC zGkw7XS8Y`(MPcXpeY0jy@0q?}+Uy+FHbq@|6$zKMlV{9ZPzdh6qIk}J^^AFQ=PsBu zd%>J}b0=AV?Gjc{k+NI8VCI6QGiOhqHE;HWi4AHB?TShY$}&bPm&{tUVD5s&^Q$#v zl|W@$?ZP<=7cE^rZSIT|1to#-xd}CpSley72N!(x!7} z%$z%S)`Hp7iotHxQl7S8(wq)ZsDj*w2t_H|m2(%(nLls#+&R-`&8|0+Q&Ip&o0?eE z)|m@eESo!L`s{i0=FZBMS5jyN+a_AFX7YmBGv`fim|!ENtN_ktA@gU>n>}xS?~D{_ z6@^wsb_Hem{>5{qB{nI_!>c54BC%UJYtH=nbLY&QJA3Y&E>Ji^Y!k0pJ$uQN88c_h zo!_ix$k7Rj%Bk$g7|^eZl0Zv!>0Qn0`i%HlAPv@+4Cl~&7M4K&Wu@8XUpt@zr`~{OHEu1s2N?Ts3QIQvvc#D@$SvY0Z!nrNVt%}^BL>suhGtW{EGz5v} zHQUwG7SEqOZ{qyL(`U?_6Dp}9->xX6prSZq<${^Zmn>L1XYS&K3+I$7wJY+2Qpk!W zb7w4=K4XHD3}`4+0AyFd>UoQ2%vm^LlAV-_LX#qglDwLD)T$(X*=CGjTs3R{^!W=G zE}qmoapr9c1xPoKT03S^tSij>2;Nt5Q!nmxPGTozQZ z2*|5QIxd^JVBV6ceXElsRYAVx1eMf9OYLM;3Q)kSaI%~>|MN{XNE4L}~DaoseBy5|xXwmG2ospmt6%@8c z)8|Z_G-ulEIbl-DAisekZcdIeXg&qGwvbnmvRyJ`@thfRW-MH^VBVrRtCPi5<&~Ng zc|ZZ=v9530qPf%N&TN>+v1H+tIsHc95}Qv!S-yAS%!N~Xr<8+g&K6JtlyPXAv|wh# z0+tLZu>aT;l;m`RL8%kfK53g3lV{GIGi$<(*|Vq5SvY5^o*c+`qDo4tb7n7CG;8sa z`BP>uTsUXJqFOC^rB+a?(3~-Q_Qbv!Q(VB|&aWh|BJQ?$)}lFc=Jx8zf{HCBC3&fK zMO1sF?AJ}3KX2Zm3A5(Tm^y#n#5rY>Af+PmDngMPX3v~7VbaW5b7oEH=$JmUK&4HQ z2jtw=8S@t`Sy-i^(5c9)peog{Z05`v)8|&ofdZ9LNd;2YAm`;3b7#z)F|DUPJGpH3 z>^Za7L`bU0H!6xLC`*TKnKyTKS9Ix|*>h%3Yn{1pewCU+t0J$0ij>XlnG0vk>j?yx zll-7yPF*%*;go69mbyx*D6}XtD6}iuqUYina~935cGOl>QB9dYchT(0ko>14ry)?k zVaB4_H8M7f7tC3-X!)u|bE?%8T0n_HRV;G<+W8Y_wR_7bD|9IGDJaXB&0R2W%9L5l z8e~-E`W0manwbk1Ppq<%QC8>#+tI#c=7JTg<}3;k(U9*`L^Q2I30vA`P1&TyQ-Wnw z6y(*#L+8#|)IZBsQl&vrTtQ>Tnz@tvYxI;Q!WPb5GIQ3fIg85WKoQ2RpdxL#dG^ft za~4!9bt{51ZpNw^Gp5X#Ieosbh^j)bq8+M7>{iTJGqu-F8azp$*|>1p+@+JW6cpst zL{qlUTi91=A*~`~v~bD%*% z6=~ab?R^V8<&+f~6jc4<$d$#% zVNjrzuAMe(?!0-kCr({B(Li2Fp%Ww6*3O@33odY#l@t_JrE-=`Ub1%nrlm7V+!Yj+ zz}30g^eGExPMbL^R7ypmQISVpMbdT4?74Gh&zaw-+NUU_pe)ZMvk=Y>aJEZ8BoUMS5Q!rsh&Gw*8CasdIKau!2^o!+I6$%ELyUnQ2|u72!LE( zy>{a4nKS3ln(inCiUlDBCB^A0`X_)wY{8;wg`lds6J$9`+^m>Vt=z82qNJcAZPPH9 zeahtiazlBLkxHPbOSI=5B zZ%Xg%8FS~9Yso8hDT<&)%i2kxMoT-Wkdh70%}NdNGm=+SXawazHLNB4kcMo zcU?wKsYMY~5*SWjIDgUX`Oy-pps5HYIsN7Hr%#@{aQcLKt=jTRP#;Kmu9`e`#+-#S zr%!4#l?9oO?t|qMASDF5f-fbw+e<4e%v4kWRh@Ge z&6z%XSs5f6m1PYVE}pY+-jwOH=ancZDRhI23(<_VGZxOC)H`q9ln_~Eg>Ga66%~}^ zb*8rK$tx+$1O=SBLM75OejkeKPJMKc!6nLB&tY+q?*1!VJD6j{PS74s}mT*$Pp znlx+StP(|~MsVRRmOFRKk~s?(&z~6!ahklUq}#gr3ujK5*0*q)2bxihitO^LQZu@w zRTO4{YES8g<e_7n=!B3O9blYjAg;KnY^lG$olE? z7A#!2cxI-WLLX9)wJNeCfo9caD{?8Q&04&0?qpD|Y63MiWu5!y&6u`e=ERvrQY!MD ziu|B1|AL8gCeNNdrPEznMWGkToEAk!1)2FELuN0WIe$@@6lf%XUtUElXyNpQ3l}e2 zy~q^YpAb+`lGmCwZ_$itbLLHN2Nh7rrnD+DD1!}|ykO3perIV=GlEY+RjP9Ntc9~C zPg+zdt)kElN>Y+en-jywt~k^zh7&YwMJ z&WsMNHbp@tIdzeY4b$h&o;z>W{=~Md4K*c(`Au7_=D<}3%n+`IhLy=cWy0oWz>Xd17 zJ2d6N&3PpStwk&5%v&;d(&R-`yyTP=`jMh^DX7V+D&f9r+RQoALH=l0zauPmFjVE(+R zb7!mwkyMrkt%XpM(_gS~#;iFD7A@#^lTk)9f(KNk<*%PQXKK6xsDR{EP?Cz9F@46I zIZHd_K|_e3JZ!jR=IrTn7tEa6?<|dKz%oT{c@?RyRkP;S8_I&Rx4eo}=)BqUXV01L zB%%UZsG%ZlwXAFAf|=82&zfwFrhB;}hl1Li1+!*nDk}9U%F3%q`plj)Z_dKSS#m0% z=8Cxg`l(Cj&YM4Ra_toKK;Y%$Qm(sVP;pan9Vnsk0_c$(L6` z@#bQ1-&Qnk`}DS*XY*uPPd~siOf@ug?S( zVJg!0+h$FkF>T_!Y1J08sLB^8aw*BFN_T8*43`776$F(OlvPDK*UXr+aMslMGm_+$ z6lNefW73n|Gf$w-uLnmKL3;`#HYwK>Qrqp9amP?AxawJ}3UX%^U@ zQjW7WE|@-h`P{iv=a$VuGJ1g`hl2jJwKbp-j2WQDn!1$D##OTy&RH;Z#_UPcBfxDV zL=etZrK|!%iiBVAjS?ZKxtHOLFrO@d-a5kU`TY*zvN!kQtexb@|Wez1(Idc#v z|BDQ#w4Au%s*ct3D=a>nbD3Ehd#vqTyKL#sZC|!?Y}>iHeMNI=P1VPmZtM& zPwi_D04+s>>Ug3k0iL%Mx1T<_r?0WuMGB$o9CYHcNs&=WsZmh^JZxTS4Uf7^sg zP`P6QiI<(A^b8t0>Oh^clu=YrR8*A{SB=>)ZQ9(a6DRhkf#xisx|$Ukl@uG5W+<}5 zW-UA6la_3%s%mOVs;UiY9N>vT6;;r*rG&U{_RM9ICrq2%+uGL`gXE1SMJ5G#g+_Vk z%q3{XR9;>II&;~s$SN-bnz(FKY*gS-1Wk}BC@M-eh-=A9ii?Sfi^<4I$VrKbgJv!x z7A~LG)!EtG(=%~GQ>2^{JO~>>m8}|RdQufMYuTjCpwgh+s@R~ytE{37o3~T}&0D&I zW)b_RPoLP?Jz;XsjM)qU8&g%$(fW zHhFT-zW$#688c^0o?d4xrw9*YP|T>!Ts42`>gCH9E?&HF@q)$k7tdL^bX_KB z`Ju8*=X#LK=dWC`c<#IvOI9vik|m|00Ghf~l#{nC@9Lb;S~PFQglU}(Z9Nk^8{1}0 zo>5yjvwvc5XWNYaNz*1bmNxWG?3>#;p?Si@zMjUm&TP=sB`lAC8mx)Sx+e8DH#fD- z=;`g7IAMB6VO~Ov19&P**1x^8xpm6K?*8uXrnc^m34IyhsmmB8C28%l?um^p{ayWi z?fsn-rcRtXv$t_#_pH9g?!K;`j_$6aFh_HTprov<+Lq>t{S(?c+B!aJdGm zRgxA=nA|sEa&!NL-bs@u_P3`vXv<5ZGX?W=xpS(VYk$5(dp& zs!CZ->F=K~bHe1_X|ww$_H?xNP3W7{Gh;$;f6tVbmZm~i1w}bI86{O&r~auEo9ib| zpD`&BJQEAe|DXv(huK|y{SA|Or}g#rPwuG*QPdi(mP_4cazu{~H;Ssq>>g6wsi+uqyV-PhC6+1An9n4~4AqSyeLw^V8D?QQIt-qGIQ z)7R13mjt#IJhTuyufMygv$?B(LRVj3Q)6dmS9eGAlnGO(_D$-DR%%o<1noYNR}%@G zF==jpM_sbMJZRn$$+LFz+9tGg_fF{O>7FpD1=KDFO-U)JO2o~XIAc;z@3hI2`f8h} zCdz|m#z0f0;%N)oI%f1woY31=ofYk5W|z}HwQo{yb4zbeXSXGI0su5`X*i>&y{9MJ z5S&&VMbUDznTI3Lw zwwc#Gv46s(*1pMo6DG7m`tCxYLI3%ax+hQX?(6EE(A(Tns@ehSn#vl?o8B{JQvc*> zlM2*j81`_yl2uVqkXMp1?(gpI zpU^$2uM;!@*siFh+&!tKuNoAlpvnanX$mUR)^k8%KC!pIud}DG7}R?LMVh>-c-Zou z$+Ktn_jmP9n9$z?3Pi}PrC9E~*2%p+6WdCfEkPskpd98mp=Uzxgo%yaG2pQT(5z+a z)c(#0$gCyIHqcD-?4G_!lluC4`gL!ebYM{x;gtMO=xZFPXN!R z^D3xFm(A&$*fn)hS5K}|t0IqP_oTLtp3a{BWXRMnXzXO_l&)-D*rX-QDv-x!)`QY` zW80L;(zp>RcS6ggsa;*&y?#=lVHwbzW#8;cJ+r1y zn$h1sZSs`9Y|tzssAD;2T7TE1&hBPgS5 z%0Z?qVOD{{aBk1Uu8ET-Pit*#>6w%&1DfC!R#1^Nncd&iGNEr`S8q>wSKpL;uw5!r zb_-iuC-(OBm70K;!tjIU+Glo8nlP=SaZZ#JXpWK-)M3k+ZY>9L3uxH~%ra0Y&S{@C zsc&Le`-Dl8`dcbMV@oZHA__|KhV$okO`h1%-QCgK)-$EOKNmV-DH^k~XUde`DYZeM z$v=JtC0T>6zLwU$j{bgsX^;gRph%mL1fQ@(wrpD0)V{91uE|p-O`Os{Ct6YkG=3*8 zuPWiXu&HxOf9Hhmi4!MGpVHCa1WKqaihK%6@-35lCU-Qp=YU3$K_besHkGZDdnQeu z1X_9DqzIa>)b<5MGH4nR$u`T`Z9V;cJ6saimb&{QY%PMXp)ZQ8`P z-YJuNCr>TXgicp#^!D|&G#XdVJTYGtGFvHa)!#d%YeIuJxO48YAFj=l&xwh@9mv1vAZcpxeYW{ELk(ZXY$mRe9%xPc(PKqW@^vG*>n3R z`-!Q`_krgTk*u(qpWQaK%~w`ML0(P5zrSZnQ?C_h0fwT2df&YM)}}&TWy!$F6Q*_d z^z={3fli32NL#Gv>zUX;u@E|0DUmq4yQ{ssyK|zaC}`{#G+7C=1Qb}adlqywfF?&2 z6%{nfCU^EvZ`A}%R*J>1nmD;J&s0W5)?n)NNxiil-95q3$x0Q8y7`U$6Z<;-z_W;g zpu#n1VMkrdgbCe~=7q_^CM%KcnK`AkFAablmjw7Qa_#@yLcdYk;@Kn;WH z>HQtule;?N;gglYD>^1m>TRwDjV^XVCS*Kk_s*C#xx0H}--03q&@6`-QX&Jz%fjl$ zNzUN14?J0!+f+L-US3jBv~Fol6=sNsypiT&NRHIdMmQSF}EKe2bh#A0>GRHdqT?);9P{)rR%S~@1TfTm_3 zQs!*)jqkqqo)T{l!Ip~rJPsv z_V@MnO)P`XR8~&!m^67}--NEd-cDC=bb)3nVU~bGY+hX@Xvq<%R8mlqEu7E-D!3cH zq?DV$iMC`xU*D8zv&td!d!RKcB@3E+d%7p~blOToCn^6y|$p)&$J zkqDZhMzXvPR^if%A~3N6N4o|aj&2xr#E|2SNp^%oy`+K zb7DP;f}knnIc@D-{Zo6pTC0q}Mx#4^RuiaT=u%`uo}$#~nbOzY*;%Fu9&%TfF`m^h zm2FB-WB-IiC=i-dr`~< zE!&w{4O)UXQ&ApNQ%;)F*VQ{6R0x5l7v=P)OzWFGvAwHzLKbMM4^%*iCC=}j+}GML zVM4nfe0mbm^*N#l7_yC?Pa_xAL9$be=qI1!~(vm#3nXw?#Ec2ZfkdQNNal%6a_ z(E1F}+R485>3x%@P3j4g22EpwW?`KdPMX}^-q|>%!x`PE21RxS6{)@k8R+b!O!>^N zNfY|P0oeeauC$vqZBlo4Z~xRv=0czgxDX6vgPw1}i>Z!;vlm#uT;FDL83|!PTVbbI&)4CH>75b13Yf)s0 z0?pfi=O_E8O`g~mAp>ewfm&MjO%u90CUv*=f;#Vz`N@t+&3$dXz3pIE^&%V8tO%Q+ zoG_(((qw;WP=B9aK}Fnma@Uk8)27dx3YrFOR|HK^YW7c>+SS=Np{vSPT3G?bm=@R! zW&5PQz9xI{!VEqI73usLy_0)dTP7DkW+)X@r0kbXn>b%fEs=GGOG^upV z^huNYr%q_m0|zjuys}?5v!knbLT7t(HYiD8n4uzVIk%;$qYLDX4n-a%ne6(yj`q&} zS`7uIX3*HC=8Rc=6Q}pLwoYw#0nbq)g~C!$$3@a*PG@(2r;9XbW|mh$STe3sH+dQWdx z|D>J{aKb?`VW}dwf{Ik*+@AhoP#|?HDk!K(`%mbd*xT1-FRB8nBUGdgnw5 zZ3C^NK+(SpG*LNea!+ppbfQwqy|;ft-{fh@(1}W)MeWo3CroN-%K}YQqWB+Vv+cs( zi5;MMvOYywc~yy^?w0(7YbbcSQcOWX zQ7WpteZu7Fb2@t`bocf(wS$_XlaY0SX9iWps}{6QY;l)`Pgr`?^-k#ToiKf3-{gr? zTR=@BbX^K6QnsKlt}u}WO{xhhs7P5f_V-VoIH9+_xvM)xMFGWipqV*&HL>tT6ME|+ zRpuxP%BzdUPi^g}>*;Iln^f%}51Oz<@;_vrP&|4;TVI}@ywWUCD@a8mYVpMWu4xmd zO>PdGf$9q`c~!}}S>5fnGN8#X(E3C1toc0?Crs$>Y%c~)d7`-nWR>}%seQSc;2BGK z)v0qP_H<3^?=CWz0nJ!~S z0??GDSm@H45>RCinX;6zUDeap)zQ-5Szrd9vP4opUy)NuPDQ$5acO{@5@@a!G-cU1 zzpHO@Pv@lWDDadel5)`0oV=Qh;f#70S*2N^UWJm3WWmzz&dJjzPVA_(kp)dzqNwLk zP?A-ev^X9%XDMwzY3bz7-dX+q9sSvJkb@C4GpEiu_8lifUR0y1IJ$db)bxNlR2k3JMDpIh2(Y6+pY>TJKNqb+w)0bd&I%N7%-gJJ={3)rB=}Udk9AmE8XHzZ{b3@lf zwF_s?ShwcOT8=gAmRHZI$S%zPTEJA8oy!=X`6GudJ2%NM!q@Yg7n^6WWuUFB{5Lr^ z1p}=B2Mxt93LJ`Z>VEnG_NsDUWI5!dRebaU?A7GI$Z^QasQM0i_B%O7S=mM;$G($e zR8T+`cq_*&Dp#HP}km z09pS_Ibj(kabq`Q8%uT2j(@1y7jhypijvw>SCA963f3{SFflOEL(;WVjzM;!T%#<5Tr>Fm2Dt{=MmY&Z83`r3EGK6V zJ2NvAH5BE{@^Z4WO>zwK9deBd3~~($Y>EmB@^T9D4GJ>y@^W(GYHkrhwr2L$W_l*Z zsz_R!K>aM671t0;uRVd;OpY)N*aV3r5I#eF>^z+v9Bph}13bKZtSr5qEnI`Wf*j0^_06q} z%neLTjdjgT6_5f>PEJ-q(%fIu*xt@uOHE5hTT@F@OGjHrTd`SANKxNNS4&^j%1p<^ z%u3J7KvPM!RnA3LT1G|B(%IV3R8P%N$IQvZ$k;^RKu6Emz(m`_!r0p0(#cxS$im9f z$iP_3$jsEl$kfHv(cDp29&~yHG(k1U8OSPz26zUA1qAv4H@7e{)-g6xmq+%ptfGXyzqz$ZgD$V3laaBhg}I%H zimI}TVv`)BtZt(|kDZONnW3qUu93c#u|~UGl&q|rth|nqnSqtHv6-2Xld-vtskO72 zwz-kJmA0XYiJ5_+k-D6;jGVHHx`wvCzPX8+fw6&sHma=(Qf96eW=0lPde){!7S^UF zMk)&Ot#ZQhQYxkf7IsD!))qGAjuxhd2I{h1a!RsF=B6e#UgnlYw$7&3#)d{_<`$O5 z&X#7TM&Zfb05 zYHMO*VCbczC@0?}r!6lpt72$rY+-F;ZlHv2m$s{siII_sv4Nq1fsvkamznlnpqkfYN;s7$th@=*qB(G>FF978yR)TamvdoJDTVlnW)J&qS_#% z?qFDp~=wNMTpsfJ5LCw-kU)S2q$k5ot#?wT-OAd6@iJOCtnT@fjv6Y^j3@Bx* zm>L+F8tYkE8k#E0w#jkI%1i5b8km@yXfz>PAuBJfG1qptu8D|u-x zZ%ZRP2Lm%BV^bqteQnuJIX-zAwGewFI}2kATT2aj`6f9oJ$rL&YX>JIGh>x@unk&H zhL$F}iWoL{8|dpBnVMJ|8JSw@%6G{Lf?VUIqoZ$ZWNu<+YHV$%(J99#t03v@V_;-# zpl@WTsU+JZ$0Mt3X>4p_ZfIozb^@EcgN2^4mI8|Ta`G}7Ze}(nmS#pK#)d}58esDk zBrO6>ZQPwrObm@oP0WncAm%$b=~)A{vER2nfj7`kVbQEPfLG_xHgOizqsgaSPvALr$hOLHDp*?R85kLunrd{&vCGRUd085%%A%MK$`y{<1|~+vdInb3wiX7;Ak*b# z6+y+Bfw`WksiCovv4N^=ha8W*jEb|BnVzAgiK)753n*Pmn^~Dy8yMM`>$S_V$tp;i zhUh4_B0FAAURu-3*umV$RL{=F$k5mjVXCjKAloh{BrmI$;AUuNVPIrvV4`PZt7ihXK~cgw z*uuuz$XZukwq1@(R>{c3K;J~)##E_Yjzw0!l`FuY-DI^V`8Y#E+;H6r|4pAWocw@Z*FK}Wo~5apdsHb z$0aYL>|kc9XJDc)-vy2cD-#=YBNIIZ*)}-_*-m6f%PL6g_?z08np*0cn;02bnCKbm z%CyP}%PNRl2bvfe=;<39nwaS8=olHQb;@zbD(IM-TRK>3_R4X}npzke7#SJs%1)M3 zKqPNaFt}Kn85$bs=&C5G8yOp0*=xwkH_3^}%1c|uT9}yXC~6vm@~DoPwV4JeVCAJX zTpcY<%ybmG<#=S}rS;5=Ees8fOjTqj$;l&|EUoEgXkurkAtx&%C#P;?YGtCY&@3k+ zt0?B~ZenSoC97<1X=G#V>}hYJ2{Kqg(mXlTTGv=hsZWku(agrwK;PU-PgY)bf}AL- zx$gQ#mIg|)@{Mxrx(0ftChDN$mPBRcrM3O7&Gj`Ewfd%#HQHPF9d|cQv-M zF;G+JmE)0@wXiTUbMv$`R+E;WASZ%mw5o-Lp0aEMC|=YJEzI>yR6#YHh@!ibxt_j; zoV<*-xt*=Ck&&6XrfjPmm#l)ccD%WvxtWP}uN38?vIXs#_K-!CVOYPzd= zfRT*do;v7AEJbHWOA~E<6C*`X#iJnQ=b>w6YND?! z+at#-s~~0IYHVO?YHnohu0H|QXc-MBO9OpX*%moLSuI0QDJH9+Ag3JTVPm4J2=a`q zrKPEXrJaSLYO5TNtfGWfsF9_Gk-oN)Y_A-@yo{!uxt*((v5}dXqp7TXFS6mFeD1Af zXf7w;D#x#=t*dLSD=#G{9uS~wsHxZhP68HIhUT_T_NH30EnvR0y^W2rk)f8NY`+|z zyqT$yp@ET!shN?vs*HS(oFGb=XxJK=DRjv3%BUGwI=UFk`-U1DXeu^=!e84=&&=3B zOIx`O6wWS|X6DA`W?J%na{RLL(pC;e#%AWGCi;e!hKjv%Ldb^7X!zLZDay8kB3sMC z(84<)#Mwkez6F$ZjZCbK49$&|Wt&08l9Ya!nW?dnshN&^KPVh5tPCuz%#F>BO-=Nb zWP4DJmDY6A)drRSoU%H``bK7k7P<ZF5io6Dk#GI^_6<$7-Sn%nU&>01qrjf zjDcN)8Iz5XQVYmQ-mcm@MrNkwCi;r9pmeJsrSEENXJ=w#YhtFaisDF+an368y>iT; zhG&-?r>v5>rKPo%u^PArl~M7xv$l4zGSWBI=?9nWx{mt#2BvlvMh05_=#lNBrzksD zj#E~_+RE5a-vH!2W_cMk_Xb-wdlMZSD`ojUP!LM#I%yf1nHlKm>1y{Q`wdj1d1}hf zmE)9`QS-DiF*Y{WY69D;;h?9bZE0d^X{ru#hJvJ7fTe?#nTd^yttOf?WHcPKlw{}1 zamXsUTA3J_S!uM&aVc8co7h_E85$dFg7r(8I2v0R>*<)8>nrsmb#)u%m=sL4=gV=* zD%#px7?_)=D>Q>b)X2ib&eYi0)Jzu?qP(*5GFq<2MrOulHu{?K$Obfkj*!#Rkd>b= z$8Kq1U}|lo0gYr$D??)|OG6VQJy0a`$SO!!duW?jTiBWDs!c$00>}heWn~#iFjzX8 z=$nC?1U#~e#`^kZ#-_%`=3o=#Wz>UhP0Wpr4J}L*kWFZoW7boam7fDL)Xq`Q!p=~$ z6;$i%8k*UHns-Jj;1Wk(THDvw%*en{*Tzs0SwHCXI~i9i8F|^+pvJ4Yg^{J1v5tHT z$P9HSCkq23V^CAF4^$dBnH!nd7@AvxO9R9hNRu41teT#}JUMn*Wm^jaBVAQcU1)4# zZmer$tOYCa%mNHe%&n|!O_XGj?QfA|(om3{C&w=DVrvO%HMPreE9sdU7+4saTARtp zcgu0h%eYzSndloC8E7j^1o;zMJGaO&$S#m$SMabjuryPVZ3C%PGd8xiwX^rJSL~MK zm6ug^vULFEUt=v)TbIkR%PLx#nwaP+cFJ+f>RFgsn&{~o8_39afRdn;PKcwqshzoz zE~f1ES2K|b#g*242=vxmT}8!npv2dSX<~R$bb$s zlvR+_k9KV^W;Hc5G*FdC*Qy|`?V+b{sIMg3EypFRt*v9IZ)j|yC=04+ltY|M%_t?O)LWNrW|XE_xdtc`4}OiguVo52O1hNr2Ck(sf+nW>U2szy*xM$gsK z+CodA2h?8DH?cK0G&ZwV1C{9VvdS(NhQ`L0mbSWzQ&24AlvR+lb~iLNP?qhJk4Rm~?m!pxX#srXMs>Y@kCMJez(((>JPB4S>FKIxuB$jlj$c+; z$Hdg!!qUOQ$kN=#P##(7La?zqo(48Hnsep&hnO|S}8LxYg1D@Q(G%t1zA)h z*<=-@Jnc;l=7U=!R!$~n78b^a2AZ>wj9UOcLsTo=+Ejgk9Jj1upqsg&k(IfLhAgHk zT~~cA<$0iTSH?NO%-G1>#!OFPHkx4y;-+5OpjwnyMnPIH#>l|XK+o7vO&(Ppo2-JI zpO3ooTu_Eo5DWCsH?cA?wK7zZMOHT-R4B^Gx#-Bs&H*WumhuiXGO%?pw>H*-R4~wj z7gW;AODo2BD#?SQL_yjhI@rq4+}Xs~z*s{TTopl8&X;4ARSOH!m7gOgAgd^)73pMZ zWnuvum(f>7Q8-tQT~z7L+37r8J_Q3@wa|K}DXL9;k+gw+a`?F{{WcD$D`3 z=(LhU&CE=U_05cJOsx&26;S-M5Y!lh7$>c3proOtp{k*!p{b!FGY=^gkIJ#iNXy8| z%Qnj~%F3h4G{~<+Qh7{{L3R}qpN{hYkogBlpelTk=TMMU^V18oSC#o9%^@SHnvm!5{sH*F>zb+r=kDYFivr_7Gn^F>EJJ{kg} zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiSuLLf8Qz=DCH zX%7Pf15Z|ff&wpBI~M~3gGgFQWkFVfkpcq)0|S)y&nhs=U|?Vn%gjhAVqjoo0Am(* zb`Xn!KRG|A2+U#-U|?WSN=`{lV_*SkXJBMthN)&?VqjumU|z) z!04TrotRRpTV|?fq-Xel0t3iv42|a*82?W`e~>|pMXa>BHr*+e_ik(nv%8#05jSsG zTXMWl^*NhU-{sq--=(d2ZQt5|p7HbL&rXZPgDtMO?e94zdg+JwqmP@FryjpmCRO*` z>U2eVy~O)`;eep2R+Ia2 zZecV3vt=cRW4PbyJhbwBIE}mT8q@O$PeZh8Cm+w^sGgTTPx9sIEgiqKc)m>7W~Ki4 zM|K&T7V~A*Kd+j<6>d4Wbf#+YjaNAWcDvR7&YQ?F<mFP3DN-S*#s9>y6@p#i^QbQ z{E_jVzxUYpzGsqZzulg;r|kHP_GfkrB3jbw|9&}mJaLUMsaOjYdJ2*OCeiM2d$sGeP^+?tJpt> zutSry`1s1d>It9HHJaq4x3b$(#OpvwYt|37=!oZjs%IV7r8#b%vL>qVjqTIpJ(n-3 z&T^Q%ui9zj+Wa0DnH1G~VT(FTuE>1!5Gm$-aKxMIRd~_Al|QuRTlcKkF>%u85{D=G zGqp5>6`!%MxgaK9+P&D$KprTx|72?k6u%}*4%wys-n(<-Tr zC1a-MKZWa0zqDEf%}KqqKj5k7{Art3e+|q1`0$SBrn8~joJvp2o&6dhmbSt9VOISs z;cus}C2gNM$zn5~+zb2PZgv`L|8|+_+%dkX)_HGf>G7l9|051RU1V)wx!>FN`7)^o zCxdD?ew3Iba!ezNjq_J*ul(JOKl`*-noo|}*8Ro0 z>L+h&$6c50Yf_?iowOtYij_sFZ> z{qjD}udgjU$o9tJw<{IDT@I+-;*mUY@xtB?-j*XNyJjD2eRMVU!4LUO%cd9bt!3d@ zVEE+N`$-o?FU#!tur*)n?Mc0r^Pf#QE4XI$zVx}GQy*64g&(zB%jvOFZo2Wl>|dWM z1m!y%q{9`P1lLJC`hDP0Q@dO3o=m5}Ez=#GzwTYoA|(23_O{D%pE;lWTkrdL@i(D- z4o&Ob!e4Xu@4hnY=e~U@9jX!~Uzx~cfUeUYEu*1zjPtGBs+&S=1*vdKUI=Eha5ufx{TTRGxo^e9{ z0i!)ntr(3rpZl{|M&R;X@o9^`<)n29Z9D(zKy9sO#r2P|d~H%sHRJYODvH{%`AF5) zo?gKtm%keJ&AGMiz2KZxJSz|9ALKjsKYnw}=Cb7e@YPN)6T}lb3-X($9u`|u_HOdt zl9TUt&3yO2oc|p|p5b+YX~pJ=){)=5XKEdM_QiOW+ul=8HZ3~RbxyaIms3TfCARo8 zm(jo13ezlw^Z(qEcvw_$ZrX18Wzs{_V%aPbz!7N?vfcpv<`-hIR7!y$n$|v{sZ){r*2< z`wpSLi8X=W8daT@mXv&b$MCgci&pi1?Ffe#2@21uE^!`Yl{jDRW1D-<#_dZ$obHY% z(udtUlcEiM0=7i1Oto(~{Py;YKW0wyZMzz}IkoP;Ne%J!og3fkqWvR|^*ih2b!(oh z{5xg4_jSmEv@{)|U6PeDZpELdAI!zaJEgrY9u(gFe(zJ8m3Ou$JoB`O=nEAXZ>b896 z!;B~1=ae{ERqY}l+28wdMybt%EQS!1Is^H)GvYIE*l0F;KHebwp)a#^!?#U3kI#HP zqsk!mqi*7S7X?Mjl@B)D{dw~5!D%*z|8CnZ7Fj&WRcn*Yp&s@QLvJ=-``P`o4>%u| zd~)Od`#sb4_)Ux4>{IN(y=X<+BA>Ul3p{jfsx(A`cFqX7p<8^zLGtOfRjrnm$!DsC z-dG!oh&4a1cwHvuyd}G({e8e%x5tSeOHDRPepV?8-V>Vc(0hq5?cO&D;dQ(3U3~YE z_lw_=pobyevgagU)*W%J`YHb`$2z+4;XJSAr=mq&M`905Za0~BFCpug@!pUM;g#WP z#%9q>%l3!wWcPZWw`N+R@jll%rh@bSC@uClxOm2^>)WD)HO{|Nkyq?K`BV3hz^@}KvghwvBr<2+cmDPJPF~pC_+G2!Npk`BAsr+LjsVe);6Su8K`8yIAOnNZ{0zDmA_Hk2z=hcraaRnl!Cu z!3VJ~p?mJ=L?^RkpPlOR$3Z+#C`>ZAuu=O`;R&7-l778Lp}n8H^*0qvKKwn~vf!EV z)Rw(WXR;;+ZBWQLx9V|I&I`LA#>Ll{)l3DoUBA4OzpU4I<&{C8Xw+gK-5*y%`abHV z$n8F;UQu>dFVp$)wQ$??TJu-OPTVzgZm@Yg!8T#4VBf!R=N74FrpYdw>aHi1KDyH~ z;f?TW`wvX{LB07lmjo;#6DiOj92XC)BouoZygURb&@h#nWOshOy~UgS^utG zd3Q#QDWKfpz@e|pFI)H|aost$tTp_g)Dr1yViL8w^S|%~yIK4wZpcu}pBAmVVx?rD zb;h#Qb+fy?CU?Hx>G1kd!$uXg-9H&VZ0LCRq@fJY}*&Hb%yKDwVtZ${d@&p=vaAnOBm{`|ElG0c`Z>zdSjHql8+CB zZGz7p$ZC%1_VVzWx%09@kJ{#pgX?7U_aqpUoXRJ7^zF3au=OfR;>4jFVVR_2QU0zqFbgBI3 zn!9SB?=!BPyFq7GH#5CB**+;w;#8XJq8f(qLw6e+qW`8bpLLYqr)s?EOOv=_;f&X7 z4>pEbAAWmFqITDP_S>rubltPboT8q%m&ca<)Fy%a3noraS0r8W(A?N8UvWCuiEnW( zcl%}mllKX?Gw!U*zMN~D&CZz9cUiPxN36e>+C~K_?&u6PwR8_<_kuGUJ}NI=6;=4< z(*eb0Pff(FW>0NX*MEEM2(wzoh6imkINe1a35eYcb^O81VJv!tKfZL9i=#~$%iI|c zzF9en$~(!%P2%=zt$W3BY3{d=VfFWxehG8&=s0q80dHT~b{TPr8diy_Nqa)Qc@F)! z!^w7Ua`tkkjCUV@C>g6{J4W){>c2Q+!%t(5E$?>5UMijOJj!lHN2akJ-hH z5~eA*#TG|YS+*4ZF#I_~YC(F!bMv#;0@G@iEk48?xYXeOgee{C53{z^W?y#8X?t~5 zT3YiUyVkXy2?wINt9j>H8vX8e_P1mG@zK0f-jq+bEb?Qm;_D0M@-HNRR@mrPylvhs zWuCHWsk*|{3$2-ZXVsmY_FCL%;s2UoY26!2DsKV=4>|R`4*I6^Yv+o+>zBD*4p%w< z?&z6o&zPqlt#kKzn*1fVc^>1BKU1d8a-Ocm&*%E<@M`@Zt2{F&v7KIT&~-^JRG9a7 z%ft(X8$bQG+%-K;WX7La9flX@Fc%cO=_vWPnpfrggKQU0f` z1NXDf(#_XDI*oUu^Lg*YOBHfN*QWVQloaXDODGT9{mV0`%jkWTZpXja z(&CnDPJ6zXxpJIQX}vRLw(qBjQ?Bjo@tts~@x`?=f!y?7mXhj4j4z9iKFxcUzg6H_ z)(Wf0XaCg|JZ_y--}El%kH1dGl#@&!EEjiv*_i)zVT+mS(ZgSgZU-}j_;mlZkXrZi z%gU3Y37!m(jy;>r=vn${)`Xxa3-;iMo)?j^Z!hnOTX?4ZRrb~DlvkF^q}jGiTfHEz z**NoA2iN^FZieq18ryYbOxjKu&2*Tt?s?wYjLGYKCtsc$!8`97AH zacQW;9{;wb_$S{rl`12L#mkN?Sz7cXk>NEn-7^^|b{TmE#!V^g%9bxUavn;Gy4(qlbZ(olaoPBsH;ZjTrGv1cCACBjwxylmQ-kJQbe%2}7 zRK&kT!fN8-DSxNmXSz_g$G!PlaJ8j=ImZ|8wD{Mqzo+E)CqyjV=4k(DbI!{;Nw2`@ zxKE2kde>Y3EkDl9)AArudaKoovb^SP>$7(r+IvYYH^QQ)yj+n(a|{7HJm`9D3}SbK=gf zDH~rfX#NjNX7F42#^w9$WzG3>v%Xo{9<(~3{a?}V$oq5VF1PN!7hgVSeXPm!X1$jR zVfsxODXhEwZbdCGarps=z z))X0r{wNi8t-Ui&E%?{8ck20)we_i-yH6ZjHZL=x>DbYQ&j0KBrk*qE{>o9$ay5K{ z*JD}tEB4o(Djr%?x5K>sL|a)!`Nj3D9wFKP&e+D~X-3bMiskgUz;!z4$)@Oi#%FI| zm|}IjFX8x$dyUW4G@fn^5;iF8;c58BA}G9JMzZ3;&)-~Pru5~$TD@?GMcc!U>SD9c z#TD_UjrWdAXIQRSf90!m+OLRD`!w(UYOnh;qjPCdbigMq=A<6>i7df24V&_uZ!@H> z4UFS{=d!z1>D1%kq%U!qVvEv56z{fWZ&I6*8<1ce(tq^ga*Zpee!Ti|B;Q7FtL>eG zyB{=`=dXR2!J~CtFz?_sUd3~IwS^~?g#H=tV@xvdGI_lI&zryn`xei9#y<7c8eJ*V znf(hA@~_|c{55l{`UK&LO{M1>tGF^b)Ql9E-0fSIHXMHYWnTO37uo#V_b{y4gKSg#6)8w@B)t#%h-exH}wmK{3z1GCyX$5{k zB~Oa0in*nJ9f>!#oLswF(^^E#%}3#!gsho@#%;C^#-;_Ve%2<7ee|_SrZ-=%pWF38XZh^F~ z!LO$KyZYs78Y}1bc!o6HD2e5oTv8WkxpC?v_JRxVzS}+jr_mpm%X+U=TP(El$)yQv zSNtvhrE<5OTjcTKZ+!UA%^lX%Z4=5>}r` z^VYiiHKKpw>^&7;*86TM-!-q0|9Sc`UM}Lddx0Bvgc`MJW_-p#D@4^1o@-~KM;#NmX>%SklnY(O~Arr5n|GC@N zI?saN<LdJ&f4e8&3U|D<^mIxqiZOj|oWjicswy#DEXrzIwEJW&-WSZGpFisZJnXI(WqGH=FYPFi&% z@7DY+rT3iPo?T+ZBg`iv;jJ_M%bcnVzGn(<>^;nB`8R*?smwHJT7Q&LYUYg`fp^Wp z(?!`nfAG5R{LTEEl+Tme1vgup|H$5AJDn~Pb7-b&63@PKJ{+4Qx$`+Bv{{!ZJzXAa zoXzsY$zx3o9x0AIVK1UTo1b!In<>$|sadM4K$)xFiy?38VGwsNb zN^VQ9S+&u}kzZ44;noR(+7sjsSu375OYqbEW2x1Y_VQWl&nufXWd1n%`E7UEcJVBK zrf&CRb=OVLqQ0{%UHa;l*Zqt9lP^tKD9Nj1L(BYa{l}*}CvO&cmOe4eAVQ=0 z;YA}WTlSVjt*cCr7X7~Yd_#ZPTwe1ZpLD(@pLWST8`jfjeKyH{!>QN*&X@(5n_pXX zqPZwOwdjfLe6fSSY}VN<51poDCAI4IP5Gba+x367NPOMX7iqaXG%Y=?muccd=g14( z2VUM}P_lXT{6WXl;#qrIW%&EtelMw0u1Rp1?z#4+^Rf!Tk_%6+{|{v=c$~H9LE+ta zc9{l&&~?oRW`EQQ{9oPre4^4et1{DH*$2$6rzmr874>ZMYsx<_VR7=M|ILes^JZ~$ ziK^TQ=Ko!)xV8Ky=f~%=77ctEIg=jFvHy4Hd2DNu(xK1C9;Za}-e)rS&!<-?cYo&< z4N2~;ySM9@>|NJ9)n;Sck*$YHqh`K(94?#QCjTOFpQzNkCAyg$4SiC-W2x^oPUQdhDPZ`JtebYb=Z5vF;-(;l6^~Vnm}Z%6{=jmoyJl6ZLX}oZ~f|x=)5`xgZCF7uSi{W`1t3aGYwXq@||$5|C;5Ann`wvT7Mse z*@UP>s0-|K??2c6?qZel%-fTqt=_6A**G6P;d^NTsH+FXEJ^>49GemD;eYGb-9=vt zUgjF!-?C8dB-<e5)Qd4ET9n@W`f<2nsnhKAk6aFa!#2w8 zRk`h5r~1FHg=4Sk&FwS&o=>R0WU%>CsIj;1q%$2RGaho<%&0!y6ngc|z02EKe|n0X zep5GPM4QcRK&cno;!n<^Hl++h)(o zKXdzBN2R;V^htv1UC~Ew%dqI2%pyJMD7tlefzjeb!Tu&IxZ(jWJ?X|C) z=bW6}?i*d<6xq}(d+p2VB^)~aCWkIW*xjIA4DvX;p@vGZ^H zA+5{<9wdOIoU%IsZMv&%j8420Zq`i-|1Wz+_IBQej=tBn4KbdxR6j26DcY7R93dco z%l@||_q$nbGXf%VUh;;C?_8cEX_u~$mbRF&Q#StWWZ{@Kp$3Vod(65Pni^ib@Tup1 zp3K$#Khg{jKrDigA`aSlxy#izv)CLdGxCo!vwpXqIokf!j}MycR~hOO<9Pc;XMg9v zbc^X;LS?zh`T(titje(-Gu@g#=*{Zy>F9C0YrDbG;lSGmS01n5@w|~eNO)thsbH(| z;>|wt|I>20`XSapNY-cKq83x9PmT*$RQsg7daF(!>ocRE-|fz2rZyf~#~im!50kXG zr*-<{%cu9=JAVBiC>c>M#`AX9Rp)$_ncozDEA`KB?>%zcDaGf&JchgR;!H8m86)p~ z{HnE=Y5RQlZHCfrQT8Fvt1e7rc`v_k5=*sp%tw{3-}Vv*yAGd#xBx;n2Rs&7|LnHO z^(GNf{+GM8zy8#^w$meChUOWt=^{HLr-ZG4%X)byr}V@KR_p6qJd{j1<{!Rj^ETbjsP?G+ z;=8O29xId9J=~^~DS1Gwc1|`!y?3)jqUo4*n_U=CS>z>huh9y?KM?4DM(iG+;zpy9{X0_^T;PiemBR z_GjmgOmtXq`jf#2`yk;@8Z*AwFsq0DtNME;@43rE-iy9cKO+1rdG185;oG;#qyNX% zRa3s#sBQ7zW+GoLx3MovSH$Yd-bF_mj8=8J-}*20?aGtcQwvu|&TKhq`C;ls|3k0T zj>qROxB8#b)EiUk?Wr}h#h_-tJ6q+q^Mz-VleF53<)^NW+a)kbg8jO<`JEM8?iXg9 zcplRAEq;q3%l}{(k*V_ccid0SEBoqX-E?{H!cMKdIZyX(`Pgu6Dofvr38z?By83D} zYTYkpnW^@7k9x4ZKfjR7=Cc}|H%*QPUi995nY-D;^$Ew`ts5<8K2N9-axh)Bv+1SU zmMX2?k(-|L|6=~~XG_Rmz6p~jWW8@x>wdqj*iIr#?1lE-S)Zr>k8}N;_DH>?Y0V-A zn;X)i6&b;8b45}XRXhkjr)ik-k5TxqT%ZPHe*bfBIA7F{(7Fxhx9&G@_hk&-WwB9}CF4hECWpKJf)L*W|HA^Tmt9aONpv{C z#CKDJd#3h@!1dpk7rxTF)nal#?Qzbe7M5tq;$6}U^wynSyx(Y@cI>vOUHhVqa!dXi ztDIQE&^BjkXP&UxpD8EzEt_hybvN4z=coJq{7<*-d9%mfC#2moRPnq?gj8=*+TH~z zRkhNuB^koC7|z>075+MxrPK0;PvZIIZ@I4SOzko@OVIi$q&NG>wK&DuE7xE9YkOG7 zAoIz@hWCf#1>UdF-#vNHr0y-x9Oo9*3JY*Q%}iso%+;NLD=GQZq8nk0*nTg2I_2ez zt20GpYS^{v=f3Iqq;Pg&&IYe4v6b`oUo_oOeD1@;keT}rJfG`gG}~{B<0gHPSxlZ= z3VC1Kt+=n*6vy3n#s56N=u!5@yrs6AtLHMv2c(r;ede}#{pWq}ey^2vd0Ffia!lQL z*@l=Me`DfI&Xx!No*c=dc(e5I+b^Qcuin-1uikdxQ;yN=l8>j4*=Dw#HM3FGbl4xS zDtZ5$>B8u^b^9hp_pIOh>Dr+e)7qc7=9g9kwokmlYGJLliYMj;+oN?)MRxM;;k+Wm zskkxg{vKl)i`D5>UzflA=@=P%@}JVNE@#_2j4vB%BixGj@fYvBfaBJ-w@$4m_Wm+Vxna@cwqSAm z_I0X)i`UL{kBPf}kB{x)*R-?Y3xe!U1uyp5^lRT1Z6@WG+i#cGWazw}Aa^a{TKuW4 z&mSG$XO?s7>W&K)HFr7ZbIfA+^Wset=d3Q72b_|B81#0WpSa^Vshe+ zdPbq#Lp$0oeZ6M=`^@IEsvAk@fr8^y-OAW!0?(yD$2<#MYNzR2mGWPCeYl_VdY(8&jNSM)Y@aD*Q^Yo~h^ zmk9;PG3d1ZxaR-O!65RFm_m`$oUIN^8L!?ms$wkqQQy+`th*({cFBQjcVhN_OjPo3 zJ2^S)Z`(<$@TV&eM#?N+cj?wSon^WO{z>n%Z>75)dc57%`)13xYc5kR>*rT7?4K0- zYD&f|(RZf)MXRzu$}YNmYT3P4lMTKycjliK<;%-|u=ad*%&IP^7^7 zOP4v8&0A;xrRTCq_ExhGm)JMH`u$+($7Rp0`kx8=9P|6ambuF5Q19lGOKZcPSGRDN zJwCSZ>FzVR&+aT1T$^Us`6nw&AKSr@a*sDmp6gBatAAzp93=uabROZz zzQx0|JJI3YWA>I?3V)Wiq_8?N?aMP0p7pEzACJo9X-i(Z?wnw?<$}N|6W_zh9j+^~ z=jqO9A($zxh`(k`k+2OJVMNRiRxEBDPv24cx8u_W;RU^BZ98`l{lC(WaL_eqwn@T z+?7)CHqzpV(%a7q^77M*m5QS8&gB*j`EXovrlwtzNx|+!-POA-7*zO!k7ZS85>)98;g$S7|-7w!_g6Uz%MjjhY7}`E}yk#92hYK0MlM+mQZZ!Z!1|;BRMEy#INBPE7k} z%Pl)I4?o}xI=se6Ts4M8Z=p!@lT)u79+{g8p1mkoDR%4ar7IDFGgeg|ujQY;y7pay zK*+&N>pgd}H-uja{B1ws$&QC6;lggax3ukeZ5MOpc7vksiba1m)TtiKH~e3ERn;Li zS!L(x_~twFo7_dph0kq2^i4y9gL881)~Yhs*YhssZkH)VsV{2j4V1z!B&Pkx1c$?BGsV%o9LaN1t)Sw$23KXX*S2+DOW z2)CH{nBD?WO=qF*(yFxsWxaNc?*-hU>F9~NFP$TE#N zlM!Eb@|82o(e}7k1)dj-azZ!q2i>#&I_L8=xd63@`UuCVB5}VJZ?t|k>)JV0!hKf% zH2yW32_64LE*QVJ^mx?z`{ows#;gj4I#z}mxdLhVyz+C`iN8MVzd!4ZjE6?b`^5)d z1m36%J^uXuqASs*)muDFCQgaAzn}TkCGIO9&)GLM&s^u#c^p=C0QmrdrGhLrZix9j zX|Au^ipe!CB6s?H*3~)`u9vbn?2>Q9YbMwGJN#nNM*r_04!_CXe2`VePE^Arty9jY zb=lllp7K9+A&(edC{LOltX)yPTv7k2QiqFuysP`q8ozh`9*tT1gYV?W2AnbalXAQz z;`mfD0jQoF`RL^^zn(5peGV9^z-&At_N@_FQJGqg=Xbx6zrQf$V)LSHv&-vO%(1=D z5U!YXxMYgX%WX5Cm1(SR_t+7rQ#s`bw{oiCZRa;NOfILG|7*wQ3M>3`dt>ed5ay z1D4nN3fZFj?OSZFTOPAvzVYML9JebGy|0y@ef#@7wfVt|y*Zznqwd+Pc)#SY&&lwI zk2#OuP<%SiZ;$M{FyA>-SN-fa8~2*xneK>;O(2V%BxSdlt-M%MFpwd3Bc zoY&I&)yY;{`R?DU^>$zEW6T~dThFmY{Mhe3eGd6XMT$$Gtlpp=@bA~I)G)7;x7a5= zxg_(n=5peOhP)*j85wI1X)ZP0_UQASi5XD}(^BVfg)>_(++cV6K9k$em0SmE=1ozs z^ZR_DX6_e>t&?i4EMIaJ-JZ0_re3anm*s>S*SRXW=NcUxj^@|inen%6ir}$%-`;cCVP;SLg8SfmQs$H8UEt-8hZ4=2-k)+sF8Y zQCQmb%XK^TZ_8cI9ky{?d-2%D-18>p-FDX`PRGTFzH1fyJg3L$(S%D+SmQofCURc; z_f~s_S)10R$G>aEpLxD9`|Bkv^78o(x7$fe7fhYc{HV95qi0Fo3cu&a>x{qLe1AUv z<-8m3e;sEzRlCjh(!;xo9?8t#ZT>82oyXGrR&~<5Q+*uk`y{v6SN+mF`p5EJj&yk5 zYc(ep!|n@PH3JMcT|ZYk_nSE#nzFw3`d7cn$5|>n(tDEb zJXySU_3}osjEP@tW@lc0Shu31lD#up;gn-?ZL4&(X{LBNBbWT#GOaD3NK2+!by1t@l7D;C6MF7ycA4IOaYM9EW{SIn zVtP^yo3v`?mmxL9@ z&Bo1^%Ng!0in>=Yos0e8UWpeHZ#LiIZVUYPV8h~i7gN@q^=x^J0Y`V*>%5hiENS;L zN7$>jY3Y+D)te!%9=qqe>0~^x-5;y?tiE{72loHcb)m)zKQ#{QqB`=77t z9L`xS%1(+ouUj0lxA9(z(-kFw_ssj&>#Y?>pmO zelmzD=}=$Cr<7LVnG@n}o3||aR{dOQ+Oe{Ej~<)ts<-|9=qjJxB^{1H>S+*cV3n6Q<<}p>B0U#vySeRRk+k1G(Gs)Rk!ZlP5O^! z?7rV2bHrlF<|H5Krps@lZ_gBF7rA!fh_lE1!xeV|{Unsu`Twu~J%hi{(NFT{_6kNN z3&~?gXBE8I)Ahmawz%(qrVPoJnT&se0?T%?d5C4!Y!RtnJ?DXN*_pX=H~i%e9Cq0A zs>(2iC3k!_OR6EWs{B&pYALkvXUU_-_QQg#>cIk3g!|o~je%uf{88W?U-(iNB z(5~O{N4n;meOP+FQ17%Ef3*0*DAvoX_IZ8qpP*r*S!Z#1eG>mtRrY*|09EfrUb`Yx zlN^_DD*LV9_J!q7<$_IL-OO$<&e^TIE^Ka0sNs534$+x=+}R4BKNkJHg~esNU;WgO zZ2K9kOC7XCQgs)fdv|-YQ%B|3v$4mUql72bCnc6Y7RWzUW!tyOZuxghE9>1mM1L_( znW#BglFdoFVaMtC6Z=l^M1PvarSOp7T5PV)MC%=f{0s)B{~27xLd;9#dgsfRmrryK zme%N2nLA11;>ovq^&fP$#XUQ^NT2Kc^-zQEYo*Tr9VTp2xg;;7KK<}dxiFUbJ5v+4 z&eD5YCUH)&;qkZcytT3Keg}Kz8k(v_Z8CbjpMx=G)1qwkcE?42U+x@EOI!OwwRnr|SIg^ne_gcX zY_L1o{=(I>?2{W`sIkR_bKEN;uYSwkAHx!tYWV5RmalBHZC?J^#&f)<)A`4fgs7Y! z*JZz%O?=m{HlM@K`Vym3;8BKq2W4m1iE7N?7rGjMXj#&VCTCgcYfQR{+nWn~zu7WA zc^BJicg=U-!jFuPue%?feJeU@tAg_Wu$7+QH_z>qfA=scaJt82gEM;yYNwY9pL%jg zy8cJBTFHs|E>^e8woTDt;E1jb)t;BJ`QOpcQCC%p=P^XrzcGuwF!?p#_9njc%uv$_ z5|zPcOSo75o+vZR_FLmL%Yze~H@-S?gjwJ~$&xvSCDGSE-OZmJ9Y5unec~~e4N;cG zXG1^CO_#F&TzvcY+3c9JUsDA6I&>7SE z=ID$|h0a>t;#K=B_L;CP;!2r1ujcfN9Nmb6nO%$5seHQN=58uAX|a;V?#BJMfBe{Z zFP>!#5Ao{expS(@G%No3l^-uSPL&=0_@VN8+n;tLh5hEM(k)K!GL?*y zWcXR4vqP(?Aw9|_-eb>S^_x2Tc7zxl`>8s`%T%;nq)X7~bcxyTP8T83+wH|}ZVw(u z$3>bqs7&2ixN`R1)r*67e~l@9=qqgT`;D{eIm=_bZ%lj{GaD63-^VRm8h=M)=DbTk zcM0}Mt)2QnFGWGqLSzn?AFDdk{6(tbd=NzxsB&J_*UTlH9C@m)G|ij+3oUKa;GkSRRU2 zxW^i!^M|2%qNkInw^i(QQA?#J$-?}$-_2`}sbpN&3o}+L@+(?jlyHWn=wxEY%gA_{ zRnE#?S_0~7Q~w05bMRVyD1L@XXAzgJoTx-a#$k@=+OrAIuK5OikU#(QZDZHkX)Amt zf4dUg&hmlJGrH36jNmfnlAYR78jw${Y_q52ZvgFD`D zF`G7J<*v9|>4ePzS$0yAK3g}ep0-h4c;?Y}d-QzI*V^w0=un$EJwQYJ%(rF%zrY1S z8?I){A6bwXyf)*;vsXJkRhEBU|BiK`rrPvHGCS^z8^~)|Z+YGKKi4k((&oQ+Ick6E z?D|sH9`m_Q=H9(63s)Gts_#FyAo*y-Rgs;g@+Bwc-u>8f%3;zYw$KG0Tv7jH56kTi z+2nTqh3Vs2H^joLv@-sCE>@7@nYieLwZf6LTXYh=C(S(caMzFim{yz0l$cQaQf{q? z2XAMa@_A{NeE%QIb76C|Ou6jQFJ-qqZ%tqMar4G|z7oHs_D)V=`kXg?-43s|2b=bO zRqE#miP6s6sl2~PC*_giyBR!Zr$6Z~TG(5)I8nR%nZ(iGXMNn_qI_d3jU*m4cRc!d zMBOO&q3X!b*il%KF|4dyt;dSrn!q#gnNBu+(aGpQGW_IoOXY*U) zS1MOB9{YY>E#LF#07uR~1<}!X7xp%#&Xxuv1uXr}@UPcPGMNdY8lH2z3B(u7P>~-GfF>ryYE#DXLjn=81~-NkCHx`mEB@5QGCoK zxWifGwf&V7tZ!E)nYjyB_UJ!unfo|EIi#+9`7)CZsZ~!muid$VZTBxpKJ(Q7MXc`c zvZp0Vv>3ei*LCZDP{lX>_E3sOG~|4zOenv z+`B?AObiqSo+kK(wmd|{&NTcB=@BL{PuU_31n7d-DM8wv{ zliwTjd_|{!w)}oXXYIj~r#wbU{ju{q%qxF)RNedc&SXh{(muyMD`w|N&zZqIzxcL1MwTo?fV-}9LR8$dH!_6i(9!h zeZ|{4IUF}==IQ+}TWP@bJ(-)+!7Xa)%B*tFqS&t<>=&nAG?D3l_cJ4(-*?62cQ*S9e%#AGK22RUm*rBD__aS%lnN%A z8{R&)c&r(^YjASx9$T zhJX8~^y$%_`250dw@p2o0W)UvcnaK&Fke>Oy*@*#Wp}@n2hN_Y`m6r^nB#mH%9>ZK#;l zwa6(@;Be(t!DjRHSxUY=H^raV2#K-1l>>mf(zQB89-v81#M zjVqdt_$M2mS+$+SUbZadBad1Oz%GIaeIGbX52lZ}ZniyYmhTn{-VeEE0&PEkWyCh49E$S-C)ctVs&f0Rn!#A7D+U{E3*?!V* z&XP|%?>!ZWD_t&85m)u0ST3gXgt@rFu30*4Y17V3QLuQIAhbtRcn#B|>Gd~?mfyZ1 z_j|Wch5f3DpZ{8L*-VYS%22)I+rt%;KdOC<>lP{HUv_tAc*UOu8K+MsuPV%Z&)R%q z#*_OK*3ab;Y&AdfbLN_3f@@ed?EhbH&EBm$OWh-IlZm7U_cx(4lg=>(mP)0H$v(K{ zJ?Z!3c`VDt-(KAIzy2qKaMYVg4DtfY%970M8V7`J>6q{%aH0-Df-c zK490}?7cyja)q;AeT@CKLZ`2K#@+HQ52qE0vM%x3BhR%uass)km@QOi#85SpS=AE#CY# z?!%%RZLVw7rMUQoU82$_il=ZOIo@SLP7Dq_m-zGciVMaYe{b7W|I2Bs91q`u zjfXzpKAiGXchilzd;XjhxVBiqHIDaUi?h7%*NguidRkd#T-nmA-nck-lXtY(QMKfBk=*<98bSE`%kWv{{(*ilOR}=G;SVPna*<{_JpP&%&7!IVJ`h zpWUDNTlM#Li|&_#JDxGLeAskFY(bn}&8)lkmv)I=cxv@Prf{{s{kORj9tgJdW@o$Q z$ZTnzGJV3S%K7Dild}(>uqf-~{hD1Q{Nv6oSB}hO^Y3}=3tju@#+`l!>t&O2x7|iZ z^Nu;No?Fv+_vVkuv(A1sI>zPHvr(*X@8*o^4EBs&UhCdIJZ`=w{prq#%Dik5=5;MC z`hU*1KQ1)ell(wo*$-a9`ZZkrwtoMGTzmf4|5LTuGSl`;zK=roj`S%%zPx$5#o;U0 zLi-~Z#d>dDe{yA7P?-~I}YwU^|@bOue|B@pZ)*ho~b0A z3cn<};&S{|_N;5}r}PAN9CzB>lr4Mn2H|M$QAsb;mzixCz_W;HPr1Kd^Ykl?Q75fYk#QX&AdKY z=lDr~Gnc;BUvmAhTj^`*8Qclwd}{4h?cX@HvJ*G$a5>;wy5i`c-tJX--V+0^WHl!q zVsyUWRGIR>{$t3B7b}mOW$Ff*9sX3~>hPyE&G^57s%VeUY^}+WIWw0oyK_CuCPUBK zSncmz<<9^0Z_YJ*%-dtJ!1qhIzyG$=K_};`E?CufjJ3UIdDztXuBJkl*)HwxQao{K zL0Y4TjTE=f@hQvFo|oD0)`L7#4BIBlhl-qMH+f^&f3LBL@m17vq3h=_GC!`ec-E3(*wcBdLh8qd9{gVRzy48wqL1hkU8T!+8meb-y%kvIq{Xg(BUGS2v}U(nj9d4*DblZ<=dTs8 z%CKCvr?1_@!GFg5fLZG^qWt1@p4G3}>pYQFEoJRz#ivU=Ht23xwBStS%ax8@0=60| z#czz8jxc|{b7}QG;YpKzSY&+HRGHBHm+9#lyFJ(2|E!+G?RkFt{0T9RMf$66YW=$M zWqw!d^4yQ_+Bb$&mwvy(G0k<$2OZvDy*C2{a%Q|;H}mq-?bkx=6CZu4-*&>m>Wuuf z2Nz?uu_rHn<72V%^f!!5x$SoD@ri!Itp``cyBnSTQmh^3D0bR*>CH200bHLZ+G(!k zPl-C2l+$PxITdBW_18T$*vQ zspKEyiGJ?i|LZR=ob=kq_gIJI_lPI4TP?&7B|QI}algu8VVrQ!)2+gBp{KGJXLiJ2 zn<%h9cEd%Jcfy+-a}8JN>dlVOuYIxeaY&HhAHz_dsM__H_;~+_1@slhaELHR&KK-@ za%;-_@R@tqKb_i|6LL+e(dcn^eA(s`FQ58b+F#-PU%TWXqaNx(a zEm4d8ZwHpHQ(CyN_5R0i29_HqIA%4hEH81^R(esz<|kqm9g<(LEmR;`N%k_k?D3!* zpDuOom?~T&GfjqX&x6O79uJSbi?|%!(_wvzQ+e*JxSBqRoFz<))wVEg$=Iv(Q9NAm zv=8gQ;2^$N$5xncJs-qo5D+O{-c$}DdsNt)*&#OskjaiRZ&YQ~h_V%TiFrBuh>!NkLCvdX- zooZ8f?XUy$wPsfpjf0o-E;qlLy1(@xSJ%P^E0!!ylxvHgmL_rj_gnoNx;MLJ7B8IH zReA5#^5uUMP92ML4zv*o`*h~>NddM^2e%!%+R0>jMgHg;js=Go*=&hS-~2-1T&2L| z`8O1Hr%YaT_Tj3B+b{C`Hoo|Maj6-v-o_h`>xKUHu2MV3ShZ2Ab-otk|N8&t{*GUd z3a!2K=m+af)5v7ir%P1tt!Q8Sk>BBe{r2mE_s*+?xNmdj@%E9GYHHz2bo^t}tx`1k z)CyG}>7TcRH_B~1yhZBvqAfqC<=iNloO#0Hx8-~lrz10}|1r*-+>jZwZcDN&!{!X7 z)4aPDFDZ|{d+tCF_aPD2&ku}0>}m1*sTI-QP#2N%E_tfY+%x%q-IzZ&8q7wia8w*` z;ISApP##lC*qLY$^xUpWnY%;IGC{Y%Ja_Ve8y>d zs$L=5O&MoT&NnH|VC~!gzoN=V;Aw>L#dPPw7&9!Tngf!gQWBr3;QuMMJtHLkT3v;_ROP}rA z6{Imi;UV*9f4%9(D_5H|zQ+HrTI_9{c1``aG_%v!LsrK(I8A-Re-lSg zuI!xu)vUlj_V4rwvoy}MIscyADKl~BI{n8Rr&afsq%;3q*TA51=Z%Jp-qrQ1q_yW| zcluO`TNdT3A7^1b=`;6Ap8;#N_~$p@CcUv!%WnHw_M_MAhw97?EvK*EDF1kad2yb+ z?&-{1yUS%(WrsgBmp!l~&3N0=CE}BA*lcfmz3f?N(Xpp(3D*|v@qTO@kX>xK@oazn zzYNRMeMgSR9^aGvaQ^?26=rIFPWl1?OC-V`->;v!tp0=AdA5JY{+jqOo9tS;XXoFq z-5U9US1hV>%}T#@z5idnx<>nDqKKR}U;LTK*Pb4}oZ^pygPo_AUtV^{KigpS><#SV zE3T{llxvXNIrZJW{;sQze0wk1TU@`p)ra-f?C6%SqFW?IYBL@ur8Xw|r%b!M>d<$Y z^;{P=YD{hw%-X_PS;q9tsrh@+wXQ2WXF9|^UG%BKd3mv52XC68l-C(y-l1t9BfHrhYi;_iS@-Ac`*Jq@dL=J+(2^pX z^;7RMJYid$e@NJ%_tpG~om+Odep&ggt3Jp3@dw@Yhcvx{a{r!PlKkYuWS;o)d$zmp z$7y$cZqkzK-XkVr@OLu zE-UnW4lj9CxZgOCOUrJ{VTRd_FWZiHvuhon+G5ti@h;)3XRC)zzUyv_xD#)e-B{;T zIrZkrzS1qLExs;zTU~c?O~n zHvOA7XY)xWg>AQ8A5YyQP+496NK8xrujs$@KSnpsh%G+&Y18els~>ABCw2VY(K7d} zscOFCe;$#C2cFBYd^q~bak5Wh&8(oV+nr})R{7a)44rstd-}O`uD1lum_=h3J<#8} zIMM0gE8C5)<5im5%6w{Xq{YeSA52%Uw-*naHvRXC@)Mi_k@4lLR#dFH`JrTc!Ko;j zmdU&d@AvHG>_2(=X@r=6nP*Do_l`BSb~=?Ry-F@QS3lm~lJN3fgN9Ve+UVS`t7Rue z_+7TBTJw6wLGLLthQ&dFOBcsC-ki%I)akVD#QzdA!TSqlyqK>xVXJ?M&NOGKsmAl4 zUtp zg9W+WPJN13+TFjq`ue3=C(b;6>QHLlDkj?-cah~DedWHHb6&yVS0oAN*M1+?0RmWjVpw>^Gki{i2Fs107alHWv~cRp9Mc9R{u94Sgj^zM)4x zzF*ZfS8IL^*N@cx|D~MQUM)F&vZQg3_J@3vFGX`^J3i@}kr2gip(vp{f3>$r)YAQF zt29RvH^=*--mYJVg-tg*Jfw5caiDPXB3s@d_FYmLe3;BD4 zQ&;A;d&Zf=4Kmw1LuG6apK4XNtK_LDsqovB$sAJW@Zv*;+NL{Nv#MPGnx(RrSJ(;c zVsctx|2=gd&*sB1(|S%FZf053qqWs$SNKPc2WPLvo_NF>AgujrW3=I^ug90DrpxZ- zn zf2!WQXfaqZf-FvdVj#v)SmCYSJ4S4tNG^( z7~**4vre`8-&-D}*y-UiC1<|B@6+9m;+{P6`$N5i9d@?(@0r^3!|27#NnuJeOz+us zv}oN}le{3yc&c1P;@*z(sm@_3k2gOJo3_eOrY|7=cc=Q@P3JE>+x1wON#j(0!;x5_ ztP9IBuLOU&RhPxZ^Ybt})4m&mi{cVms*4yeO77dr@gl^ma=ygA&dr@uZT+UCNV94? z$vJjeU-tM@(7R!)T{45tx9Q(XOVv3|RI4u7&Yx^*ylAxyySL5Xnk&ZoYyRBZb?R8g z+V(TgBn|5S?dLqL8K@N!``30x!13@KxnXNF!WjPKaThi})#sb)Yj2?V``7O3HODezAD=@;0@p8^@3WQk`1XM@|F`H;eOb5leJ6I#(thK1>2Qup zf&A15oWBIF`hC>$jn;6rzk1YmedBt?G^>xX8kbkN9!%>`k&M%d{cFDdXZ1ptt_PLD zi&YkIMehB4`*@?B?fO`|%dc1}?*CHU=3%~0L|#FC^Yp%zGa_WpO_yfRlU=31_jUK5 zmRXE}`k&u)o!5;KW9#g`=E-eou;bP)t_}~6TScpWY4^IHS!#L1NA^d-vzV*lFf_ZjvaB|WWqz+kXN-pFqo~7$npZ9?IrYEhOP9_z#mkdU-Cox4`kcMnnoUcx zvJ(8l;;%oQVR@-y@s)>*m-645_2mf_cukl@>^MFR_6ZO(o}g%&oD%N_4)o^ zC8vBpzD(&c7F9C(vFBsd{Tg>$ z%9zYPIdxg&<|%)khCh1p?dnX6mxq@-OggtfwC+R4jr{5S-`LoX&YCYaFEVi3&6OHQ!e;Clu#eFOiw4=PRkQqGAW5E5pr>#KV7n z%5Jfoc2v^BP$)QL!%RKDcaAj&LJk)wKQG;J$l6oMH@H#Zh_d5%b(Q(-`-9Cslm$PQ znmP4><=NH_} zxvKW8&;E-izxUJ&HMV9tetG8_rL>f(DI3DBR0ex|+rIr~#_r8eo@w2?{qn%S`+8p% zNM=QF^1rvt3aoGp%e$X8<(OQ)wVTqPGZ|{Rf&%C2TlJ^>2tL2^(pQdd2%5=85`XP7{kS7|bCplop*Lm!;4Lc_&kKuJf4AU45z| zb$0qs?e%|3R*PvpjkJE0Tq*I^^O?xp487evavIk03op0*`S$B?n(G<96Q0xits*q! zS6Y3NdA07>)7`}h8TJPK)9(r@D+@f+6N@QSxNdPvM9sK;#v$v>+~lbp9#`Xk`_3sb zPU7#QsNOyvw(`aGPilcJc3*rMHe7FF-)QCd)=sQnSVi?)lSFaY z&-ryny_TKyv(~kX@s(|mh?S9C_(kU3o*7=xe#CL}^q+Pzo~HLd%ho^BWqa*`%H!RC&+Y&6_QCCKoyUA?6}G)HeeL8i z-QZTmsVS#E*8bQbR9USzjo8h*!oVSCVcucOHyyD%6rB?cGd+x$67P49cJ-fSlqbW(Vi>v zturgXwC?LS-&d*LpDq>jN7;nM?(3nB>Yb@GGp}&2=~mA)5})}&HqUxv{Mhc~9# zgm!#v+~dBFTTGH^;?hM|cD*{!dTO2XmM!Z0?>=94^yBK+CnOgx**W9Id+wGRg~jK* zgjrss7$m%Z^ewS6VXoMJp7(J#%BFsJnxS(~yC%H(^?q@UREE+?2mT(2&<^`qdG(aP ziD=&B=jC^oaL68HnW!nD;`CUqx+ zr$4J-%1ys#BBa&%PZc^uM=dO zZu?$$cr0>s_u_RypQcaQv#h=B!`G~gj>nQw+2t?zV+$)hO0UfP=<_eH@wD(g8i8@CutFP)9OuJqAEz|q87kk&TY1})RIF>KERdQ;{6BBij1 za;s(f20Op{9r;4mrlvCKxDQ~2IPPq8u%3BkQYaR#WwuJuVak;#7|LLc$ z*M7yk==v|acypFd!)e9d#Fz)!tOiTP)4zH6xIa=)YdK$9tnq?viDl?Mfl1bPfBvZ7 zasRQVdwY@eLD_90*(>8_EZua{%(;m9e{jMaL4!%m_S+q1g>k=K7;Gw5l6g&IZk*B% z(R_CX7m>RTH)>Unzr1-|`P9uH&Z*+%-ix?9*Cp>R{P%>v#qT2bg#)V&8~Uk#I$(AC z8P_r4$1GAWazDoHwvgO#?`g#h%ljq27Y5$<+PErf^AY~S=bm!+_GtZLJGl4VG=}I+ zY)6~!vHWVX+WNEQWl_Zsi$zYyq%2Gv4lmUzKJmum#PgXoU;c4xPfJVMWBS|8Eo0W{ z;!T|IBxAUQT_437GT*d$b3ZNk3CGO|^4kshmY)zN(Y~rnsu8;%cwBxoWyb=JklO6I^Tgt#-Th`fV*a?ze$jpdk6Zdt zu6wQLo_M71rteDz;h9Io>l`=iT^3Ml@odwbscmmRU5#fb3H?yN{*JFp^pD~nwFmFZ zy<8NWqtbBPuT0erT>q@Jj?J)A#-nD4R{bBZ*}u~)?v1j#r;jW z_mUjzEc)E;cil)`_*kaXVe7mlJyOzrGE4s(`%COS$R2U=cymHYOJVifN3$$B?oDf| zx$4@sK5_T{_&r6T=DfEaOrC!r)rN~}`@77<-AziJLjMI0mAuW2KW-L(K4QcCyDTM| zeNV+h+=8Cx?{5k_!~ONp&7Y5b&mP!!qBNrOenaKCJDa-HinsseZgWwtXi5yUEc36t z`=RT*d$ls#Nu~a$@sD=Pt_RE zjU_K`|C^?4zGrC<&y)jOo<%UkXHVl&Gkvpl_Vqd*hiy40XU+=vlfUH6Le=6!HpdU# zxUlf$RJPchmgDhx z>CV9Cv0`BcjAvFFd_KqK6mj}ZtPx# z-CG007kNiW-dtbeq~OtbVOw^^qy`H`agp)%;bc+uu>dL#FB8_q<7g zVeB_ny!G1JG-aRp)}IZBD@0ejaeYrc-n=b&#?wsk4t88^a6CWoR=%t?(y(J{H~yH>S_^2>p2G#>KOv|3ce{|at=HqlrHk+IiG4w&!uQLWqr1t zb4t&yP@n9TnHbQhUYhpzc>Lt23f57Z{u(Uwoqb#K)EBSYsngc_ZCw^;cQjG{eCKn? zFqPng4Dsc&?KJBW7wf!_<~g`h>Xheh@$KtQnDbtFniSTyrtJOi@6+TG9>0F{U<0?o z``Zkt821i<{rLfBE{U<7hB%CLo_`*5jz4PnnREWU^VxtY zEk}-Q+11MRU*4&;%Pe83(80Mcd-C=R*hG8GirXnGzhc7Vlsfs6O@ayOQ4e|fa@h9G zmgE(EcJ$t!wfs&i929q?_)F|lcHB4XZd>sD$|JjsP2L*EEB9WF$n)Epaq{ABCBaJ< z>Z=cGE9`taO)FWNE0n)HH<(RZT6BuqHJ!-+I_1Js1gf^VpAfBG=G*l{dj33%+8M2- zCqi$yrhnP1&-?eDjCdLQ4)e7FYX!sdE=l&Tds^e++0@rqqSeO4R)5~Ee}z-X-vSd> z=IXdd%Mw~bu79!ldV|5^B9mO}{Rg>H%u=>Tw|On?&s(`ZXkA08PHb<8?AJeSDr-** zsC2&hR`KRBD3D_^<2_3M;B6~S$v!5ewd z_`n*6GU*p9natGGg0dt2XU~7LShHTr%G{Id<&1?AItpR7iw?(gdug-^FP?j|(p~O{ zIj8#Cq*8%TS@$N@UR@i;qjs#S>In19ysu4?ao_4T@lR2?(6Lo;6QB0Mx@A90#9tol zOI(}Q;`gFZq`RTuT(zVXf39@h^l2$ewoO{CKf&K}%F(Ph-ku5;t((4iEs9)rd`kS+ zt|U1odtalbFAJt0iSTSPvnY8#FXL0&mwP(%e_lG+TBBID)@$`_?iiPgi_?X@g0(l> zbr{zlU9Y?L+xAASk9|sZU)EPNC}uUdJ{0Q9vwnEaD*yP*%_Z9nUA?yy$9+4;)&)sly;^sp>UT!R=eCb_=tp3Ot$c z-}l#%ho#-Kj%*foy7?|#9ew2qP1gh z>+g9xjFlJ9*k{kPyPomU$}X$`8MYhSFZ^$0oL7O3-bWyF!F^GW+w**R9dm!6XMyCth6O zb3E&k){)mk8|3u1!%Fq)*yH8yf*#0bQ z)`MN^dglcsie-x|Ja1jebKZE zUnY5~AdvgfX~7c(Y;BfjXk^&_a1uJQqU_y4rF@p<6Z6)2MYfz_|GHNE;vOT1lZ(F| z*rspky#Cuv(N}M@svEyeeIcC}H>>q(gGQO|)zBM;`-RtUj+|n zidV0Q%YXUSSpLY$f@7WYc-0NvLf5uvRKKXJlagO@&2&ntD@WeRY^&$q+dl5)uAaz0 z(?EW|CgZKJ}r}?EJPLgB%=TZ3ii%&E3q zn>~?PG!!=odUSe=gOdA({B1&O4!K_1{>&ov(C#*UYn3V7o974qTdCaC9-k7Q)7$1){L??|P?4>6u&XKGx)-~p z#lQcr|6jZ~<*@X}1%FC+)gCv^GxqHj5?9k<0MdP&aa71#g$YSN5QjgS5zClpT> z-sD)2$MW*@-2L{K92*vtr`r@1ZCshxZhhkWohxnj#lFH$=5DO3W_dl<%{Jzox<|3R zQQmI0nUzFGe$lrFtBzRNl?3eB^4~(!+qx_*D_pW^HSgcU+cS?HIbNLgmXG_DVZ+ib z>m8ZedA|$EpN?&5SjlVNx?qy1AmUOzGhE)X}Mrm(ghiXh0!@emAd!8CD&`owJrZ! zxjmpw#n-U@0~7C+^~=pR?3O4fwG;PG_J5@3`oyrZZ^Qno3+w)uo2@XkkIyl&{Bmab z*_*~yOTBHKR!>;N^?|Qub4qtl@GD`@mCFn#1gx#pI>K+fZ0?R5asprewV$)^Rm-lf zSa5fuzmkbu;w7oM##gT-Zw=<}F*~Gnn`_$LefQog`SfONhz-vA_}^--(LA%r=@TMb zjpEPF&CU?&31Pc%wy5I9-&Od)?=a18tM^NuHr@7kn#G2%u^abSzS~-S?OoQ~@7dp6 z9!B_ozr4aKqWqEe7Trp}fVR->#&>yUv5P%pHm$Foa(U8mp0W>nxs6Wn_=H85`3W65 zrCgnp9rEy)ZhXq_i;G_{ewqB`g<3!vPh@f3`H&e>$LD-iblIZ3CHs+E%oTy!|Mm4c zW`<6?o0hpQvTru93%Wgx`@@%~%Ae0%wPbZLS>|W#+`fVN`tKT-H!Bo-X0G|qU%DcV zQ{<(<{v^i!&P^6yI5ZQ3JT=Ue^o;fME+c~W_?6| zyS=)6_RS*}3i-~uFE%=EP;?NOtl0kGtcO1D-Dg@d^~%Q|e-qP4Sj=uYRf28BR_6or zv?qz}(s1&b*8RAE=Xi%x&7!jh#dgN32vrpSujhST{7?8r2HTSVO1IzcTQd3l#Yr>s z?_J!vd%3sL?^lbYb2In@F&ATPZ=C$~$&2R`44dzLog;qvgD}Hs6G4IHr#Sa|6l@im zaj~&`#(`S#YwI>=<$YPeS{nSne&^G_d2b(S6edTNO7-mCY2^1K$zgU)(C5y!`eOdV zdviK@4;+pXyQq`sf3cY1=IQGPZsjfXe0sEO(kq@yma?ze+Bcs4*=^%A zS5=mkdZl?EHe6RMxM^~pH6g%&Wk#f0ibwtSLsr+ME;8lH`>0gMVKIy9+h$?DqL5Ej&}E(MQRo#ObuJxr~YI zpF2PE@2R?N_@Z!FX}|pO4MCRwYfH~B46I3YEuXe0t1!0Zn!eA%H|fd_w*Re+KfTSC z`*$*P24DGxtCx-mY~DM+%&GE+$o6L;JC`e*E#+Ou8#kj%PW?kn&GQwir_XhN7v7}$ zCjS{Xu6#6!%W9e8B&NE!Q&pA%Fa$&x}TT*2UpDAIqEvxx|J92 z*9y(nl4ja2BbRbuY2o~PhR>&6l{)&On@jYT%Cb}1X}zmn{10tAt;JG*E}LcL=c;v# zZ2t9a6BJgR6{?Z^oncbkoOR($K5XEM_H6hbFMSFAWV;KP!H5l_?{C{yUQ|uFXjBz;O!=nxGLO6+ z$r2$K+^*Y%?wx(?!k>cUVO`fBFS&4idBAM<3vs2(*YEB#-Di3|r(Dd4Yn6Xm$ifBl z-AEh%FGIdF?=8F7tNj2Tos`Rp6W$Ch+c9Q*b{ogg&UaESdc~I88ynU0y z=0&czAKqkP?vF4$Is3}`&Ra(v4({8w)>Jyy*Qjr~NWRwU%8GYKl()`{n)G(XVzcUV zlYfdvs^&lVd_(%6qO4qJ@;tBJOUfy!^P>-Y*cxw{r^0-Wdj=D`TDqvi;(rmB?%($n z?E8{leE!_8@9KO78fDzfr#`HY^SN_s$pXj5mZP66(k^<==Ke9ICd{<{!nE6kj_k*N zwtW-vSGkhDUE=7ZF4?KAClWvNR~@f5o$PiixwxoB@s5pAI?JQ` z?7{z(u1wY1_9_h(KGz=ZbF1DYBy9c9u%%tE@4%(K>t%jfy9lp%`SRLL<24J+j?P{h z`l}%QN{@w=nR)1Q8|Np_9;r#JHvJQ$Xpy4%<RwlE8oZ0(WcvC&^J@6~( zP&~N*X4p23#KXZSmI=*n6+GMUbJxr`MMIg8KWSSMOOw(PXT(16YCGT*!@?n#D43U= zoY3DOq1wuM`}WySnTPWVB&~nEURb>0fTi08!C(Q^rIC)jRrX8!=Gz_YnJ<~}=e1kD zV6Z{S#-+2%E(NxREnMf2!g)j7r7t(`Ql*IFq15Pqd>J>-$vl}}v-`+4uf+{}9{pzS z;8<`;Xmh9H(u)F@tG^2VYvAqKWvH1UBJ(vXjp3D0LuNNPG0&bT_qlWi^E-jVpB&E{ zJ=5Ft`?llu8O_DL%xx(<%8w^3NYq<=?InNfqw8`_!Os5~kJJ}dhVg#(-f}Kx^UUI8 zvwO3Dc&$Cb5KvgaJRx}9yseL>zh#iKKatTJCo?%r=3Wkm^A5gNquk|z;s4~jo-fN@ zwW0KD*MGUWf!cSY4TRF-Ke}w2%@9%^wB?_Fh|oc9tA!eE2R0fmm+SZR2zc@A{{7?#YeYVN2GnVX+Q9bE9^z#w*$ULK{G8XHEq&k6wAM-o=r4d zmJ-SNRxoUCjPjkcJFEF4y)rjmT)J@9z8tmJe||TYhbUz`dn@p8*5^F)Yy7up$Ach= zKjpEngnpIZ+$`brY%Y_?Ww$3=?57+(k~ODWuH5GK=~o{Yvn(xCJivIk;s*PdH4D^S zBc}!#+n=8`>n*LDB=Qsjs(Y-AXw4_JwWUlT)r`U*vrRULIzUFul3=RR!Bw<_p*J zf)~w}UQo96kE6i(=b1Styk5>?@tk(rSLE6xaH{-i^5{xL=Q6j)zL3@F&WW<5hw zZ;C+Np%uY}lh~FkE2LlUH=5SG$y-wTZFT8FwXRl!e}$X!WHb_{bHCc7f8eH<%Q1fE zI|dRFep9{*s>$cMiK)~~`AsQ!R*-__vVLLhn(p+SYPt`@ z%kD3^H0_Pri(UNxKC%~Gu)fA%;b!;TXSbe5@vIu*-=UmN-LH3PXxqNi+Q23xcJ;$e zZ|mJx-rBHD4t(;YIzzd0nSekk&sTP(`}4AkGhTR~W?G}!xu=IkulavA&$jGWw|B3y zZZr0Kc3>OZ#je9{kE3=vu+QGwykFagdxFFBYoaZTFDF^NR+9ehmM-flnR+31Zq3@t zjY|w24kq!xG&vgZGJJa5eT6AsSNNH)zBs2SV$F1p$$u;s9ATbs+%LJoX&rOKN%khz zP6c(@pX&Z;wRfMzSWXlWGu!@DrE{{`A|I*p%bQmIE1Wp(dUVFjo(wBwWg}OCy31cWZ=h8w4ITaO2Wv9*J4@YgkYAF6|f3+Tmhed4~H!L|6V?nS_m@mv=_&@<=)4A|=~; z`K$6VLGQnzZfmT)%eDc>9Bv?1zhULVf4eto(M&_3i_m z4L`Lw|MC6Yr|?Djw1sFy@5ZeXwO02|$VlYx?I(1ySd3J8l0C1=l#H*f17;!{rs-pGB6y6(QyU8EEBBr5mxy)6j`)gIpA z(>u2~>qQ6OBm1qF&K&x^{OJ$rBH1+xcVD?O}rfzf0{uUsdHZ zZ)a*{v})a6L*p`u$fKfMvU`5dyKMiT;XqVk#n$6nv}bNvz5MkOkIh9H&*sXX-XW%y z&?Wa)Wb2gUlRs%pw`_}7J~j2!5C1ZQ(_UMCcFtk`uy)taefhJ0c3$P(QPeG3$Z~Hl z-}0ixD-6GUkIqN&ER%9an)Ci?|GGA?@`mqGb9G2Ziz3e=CpH3kxDYUb#kg&-?ql`ru`W*)?Ufm%o`KrZ%ob(ZGXn`wJPv#-n=I5 zAA?EQ^1nVQrYBKO!IPyD`occ;u#%>!5b*SuP^Irzm| zS0TTSh~ycqW<7gSC!H&Qy~$Nv@m27LH}98z<7DBqKlSWnXw)^=?z^XXsvA0NE2W}f6cnMsBLcGAnHTI`u3B>MGy5ZlW+hWC{1R|ak2V>&gf zR3KgB$}1nSQ`S534kejnO=-J-du>}6f5`MMm!=0>&PkJ%nAMxx?$z)^<6PtS z)4NwJa6BEjAns>Ynxnbg3bA`h=|O#u=YDt_{Wa*5;>xEUTR-MKj~8dH*gUz}Qh90V zZ+AB5*X@jpR2RNd*(<*{{i>F0_sWUP5BZfjZnU-PPvr& zEPw6l_{xdXeG2C5nXO%Zz(Mw}Vg02R*^upl>TE%rkAEI~y|c1WL}pj^zx2m4NjY0) zdoO(TKHk9n-I6(M-vYTL!lp+Y(c${*^|))-&gBzk?`hmT-LJu0WwF$m1T9-3<<+(_ zQ`43?u9%#A`Jj@Pv9Mo+B*!J@FV^d4CRiUY&ip&w@0Ej6q}DHoo7437OTO?fd9wXW zx(T0T<>MoF+xK3YU6?-Q+1usz8jEgcD2P4DtGHkynX-}T@vVtDe@Ys1MW-7ZA9S0=Zd;JfJ2lPwYWZQ4Lp%Q5?+g52 z{ql1)+x?KF^GQGS8ElLn?Y5d_^?EY@4o=RSDlyV#y#+_EafVc{R^_Zbv}K?1Z4udB zsR|P+m#lp&&=fLB++{MC``R@dH}3wm{r=+M_^E50b8Yj2=7kt;NxaS@7})=4!7CT@ zis187;dT4|8Z-GV;GEC7ihJ=zC)UZW{FNI%uW(ID{JuLg%KEMGipkyDo6jUi-{vo5 zo#$vK+4kAd_Ws+i=lr%+Ea(yse6jP|o>0;AEQMC{?4Oh|WJOs9oljznWtjQqtIx|E z&716ru{nGGG;^HujNfP4`8S|{-g5C}VOQt)M?Ia=^kv6+e@?>{#hPVWf@eFjYl@>C z1V4M4U!DB#)pG3v3OjBbG>vTfbMW%xKXzu$R(t>ZTcavm8`G0>%UFv!=g=NpqR3GsT@8e}mq;$>{a8AE*Zo)H(q)4qZReM)Q z`A?Z%>}g_BeQ@^FqMm)FD)Hi>hSQdwohtNbdf61&ru5aF*Tl0MXO=2|jC;49b;YCY z_BED_q9&=&*;o4P6K_f`E}GiVKU+xb=OwqQPuyCH<-52w)ZB9N&$7q6KadJv`{a=I z{r~m=Q}tiw zR42!=U)GG<(~1kvy3M-w&!gMIM0J^@$djgT{7S38<@_jRDC7(i&nftI*<7~tOeIflp>%-ja?c&~NH}vUzeGxt3 zov0UE>g3;|Q|k?-54X6>7?-c_yQ5(AyL-{&{@CUAGLkbqJRGvPJPUpM>;D&QGJl}= z={ALPA-~1DdW-Xy1?9UHr&n~|-TbPomN$bh z(&#bc71Ny(%uz9-_Qst*uU%_%Wc~NI!CqzT<=}Pc|?EfZTUlMjTZOX zJ~z!WR- zXKja;c-($Eeg1xlM%k$vVu3d#3iVz78XU-){`Ph5^GVGm{Vq(iJQ}hcBlb;S-nz|y z%4NR;FX}Gldw#wwmQuIlj=_Z9@D1HNeno_F$Qd$MeChak;qFfP-=z!-96^b2sh;tUVZO5abBw6snFb0 zyZRIMMf|V-;TCan(ai2Gf#D1eHK(OFyHvhTiO-+G{_jF^$EC|@zS%oml*F#@(sWxD zwt#(B-=$lV<(9M<+s|amJIr}V?e`1$MyNP4a*4ZxShuHF{OQ}Bc9*`% zTFYNj-S=Y4iRRdILFqYNeT$YhzPjJP)pGZhb-(^~XS=R8pBH4TByoHHgx4<@Pg};r z9Tj@zy4u1^vkpdWEG^Fb&nI@AWrC&&!-6x5{?~urWwETdXZrt&1KTew{J1EiZKFzM zagJ=$#`T?1Ey**ImHtiN6(##`-LK|G2Hmd(#tt(LuB?!kS+5al-FMEiJ?~>z*0H- z`zc^+Xywe;F*RGRYzy-}8TL5puWM-htXCT^M{T?D%e)0fI z)JMmv?awx>JHPhGU)P7HU-st5_RbO8l^b-(JIb8pD8J^p>TYUQpZqwC)54%An^ zdGq7|7n9Y~@GY|KAEK80s&KPi{^v@8dG)=+2H!j!^CuQho7?ustoPOSBiq0F-U)dw zf4(WrdzaUO$}pwN-><8$^ZRvu{e>33zmXsB9uIf#jD5Dm!RF%kyL|TjXMIlTN~G-j zlY6`CZp-@j4A&C(v7P=l!{e!6+UdyCe_{pd&MjhlQ z^&;tz=S-1*0%|Ph*RmblBC5AAQBN;H-OOy>qRFbqTbOe+8eIBsIBM(9VEgj!hHrOh zoXir@JuRx5`R}G>+~aepijUEm9Ul7fpo~JzX4}A18q)jbBC68;@{@;sqrd*QGpPs_*xMK6)cR6u2hI<*c+&txj zZ9_@|SL@_@#eMRwIkw(AkLB^6MOxoycCWa7E9m>3b3WhO_eO*W)%$J2tYqeGo4X3wriK<}6d7eV?~eRDQ=X&Lq4D*n z-7jC}2r(#KZhR`Cm%OsKk4;DTZO`+tOErgPJ$l{z;K<&a-lglWbu7#}UC?>NTsb5y zLS}|cP34!Uze4x?ckDjnb8>d2Sl-9L^>zAwFExLe-g#KKWA4q@M^87Zypo!=q<|Uzq*g$ z>_?6UQeVt(AO7`J-7m7a{Pz4A@?ttdtJ!jmH|)CSH|tT-nvcR<-5jsZTr>Tdde)L9 z@5GzcR$Owetm)V7Y|f{=*y!+c`J03VCuD4&_hwFuGX8yWui_Vx{|rw(X0}X{%8=al zP=BuXYsQ;4{}v^i?zK;S|4Q(N^jkl*&DWdididSe*(u9jH+^9~twVQbxxm$L-pMC# z>L#1=L>6hPAIvg8{(G&PXyb=S^Sf$&tr3SNY3FsSiVF4Z;awZat@SLC&-Kvj>^Uvh zy%hE1-lo`aR7{I{(#YDieATvXm*4$8RrCFrYRcOUhLS(>3j>@I?w&rJoMFIe(XN6BUXt_^Bex( z%uF`0WMDLCXJBC9$qGW03CG=rTw!Cj4~J)7{oF&Qi>QD zm>9sAg`J%dq=-K`Kc@)HVh~_pU{FdgF!{NMk7 zAcGNz2Q?1F1Yfs%#b(PK@9(Y6?4c#@O`b{1Xzo=T^i zTlj^!zV`n7dsq9IUwxacgO$Hu=GKdhf!4R|t2{pUe&PRL_FkhP;dbZDji+OkXUhkb z9QHmY)O-AP0)t@YqE=Vd+MS}RUtRdbzV~1D6S=|usXgQ86dmJzj^Dqyojkg@-0Oef zc`Khei`72;`W;u*6!|~RwzFzydADY}+`scDwmGsMF*t2Gr`owj)G7RF?(OTm!sm=O zemsBXjiSc9pN2artH! zMUKSRe=ZIRSy?VH=kK|=&Kt+Jul71Psavr-H#jTm*-NgLhr3+dmIth~T;RiV)BM$o zr>~YMAN+i;A$L+^X5DV32Pbx(VHIPr`BmS}Vcf%ebVq}Aok>~O_w^wRI_le`HYf)2 zuyF*OS2b5E)k>eW)^O7MW zpS5~_M3BMOxmqt?OgqB1lp#a-M_vD=K*`l>M3VJVkKf`GVTf6OUpD*j+Z7MLU7k?8 zC4xsJf!}`3npve?i&Gueba4tZ`cBHx&G9TX(ctbi?7XNn$>86#kXP~+8<{WcxBFr8 z-`OtdMgFlL-Ffr0o?I=raM!Y!I^WJP!pz{U0oQHyLtar|Y98_yybtV;JND#xwbknF z2d@FEIawrO1!jtv7Ld^zePW6>_lrBOj>l;pD$JXwc-g6L%YKFJMSj@ zlLx*>}fOt8y^^ebBAi`jo#$?PH~CkU3Yk`n=A~8`Rz$TokZw z#x*rTx9{cOw&n>)e7oiROaAt{N2gxdIdw1|Y>BI%d{O9i!SR-lyBGhnT-?E>A|U;w z;TVfig?IwOTO>$&Q8C0aKHLu)lW13W%^vy$eqG5-Eu+3-?)}_>%KR-HGjM& z_$Oku)Qhd__gY-u7CFguX~O6AdvnB{kMO=t@4p?nHsQc(`A%Kit)H&V`Fv4pZQeu$ zg$p-VP7|E9=zZiG3y&?w9pAa0;#@fSOJu$w*EgZ1!uENRcfRyp$;gwRzHzC+_2Tt2 z1G!n(HBR0=(QmoaX;-&5GC##4)|nhSoP0RkWcD}BIbWprpAa^EYIyeo`{A3b{vKWU z{?ht|>-XwZD|PEL8%vXJSNe%LcPISWpg(0_V9rm8){v*!N8R_m7mb(rThuVI zS@6E{@%yKP=Wdz%_1~h}yZV+2!9P^9;>>)1xIKBnZR2g^e(YM@QAv$9%L4~~zL{WG zwS@V}3Wn^jts7aDuh=X<{Mz5Q+^%J-MJxqOOkJc7&UnI^TyZV- z&%(SW3=$u`?pQ|EXw`tpzhJ4L%iMyx19Q%1(R&GJviYam4k7F-PkW3IxzxwEs#MF(8 zr@sEeva;1%d)vfJ*(uuVH4FCrVln7yIQ;hZi9cp#@@=~rx;eG(zex@8^_?5v>Z1K4 z&b@6y)VnmM+|+_o|HLP3zvsSAM*rsSSgu*C7cTgxts3=7{O6sX%Y|PL+VAT6Z@u@r zs%WmkSMJHTo^6gWm{w?^)gNb}zwcqm5-IKHi8;Ex%1dt*rw8nmS>m==>izP&rT*r= zrt8v`uDsOGa^1$&-WQmj7oe@EbKn_cmfB9<8nLbC>*S8#QrYkMkpIb_md#Pvvv*mk@1eIpML+M3 z^VxeN{IRvMU!%^t{`W!Y+9`YtQ?w(erP*yiu=H*3Ovl{Sr)IFP)VzE5u-J~uw25wW zt_67UeNpnrw|#Vaft;Xhto(ynnH5}yrX__-ovM1o_@{O4{q$hH)SVWA-GzP^|I4I_ z{?`k8_Uy;$y=$zcB)n2D^4u*tU}?mA;<0m)jpVkoec7Q75%W*F&iI(Qul>eXffcD2 z*JZIUR!rY-*9oI!ta8xGnc`N z^MuXBDTNLNb47YSt)E&w;i>(?iPP@dW_M4~v7RSx{Y~-F!+BoKPeqGnw@N8$<$s!X zFCpuU@!yaN;g#WP#%9q>%Vx-H-%b`~l~>uid9RSD`N6BVW#?NPc-~+8W9o(kxy0k| zF5ZZB-)gN?|6jyY|31t2n=Fd@e5cJOtp9t{dmreo+VXZNLiF|)kXds_mY_}r;4ZQIQ;pQv&Xpo_=>4@g&XP@-CuiK zeA~*uJN_LCHP4vMa`*I&kk3hh9DEl%KIcSa_t)$fdz@x}dY|Z-=u@x#`!8!hC@{)* z$w;-+xOp?FpgcKZQf`R8R$ik@$*YV-pL`i^>`w}J)%+~X9%s0}VhLx>C62A?DqFP7 z_sssVaCZI{@uEgMnaFGPnFg=;_8s5*bJACyncqL3@j0R3zUfQiPnA8}5^8UB%=*ve z{`WyhlDGWQw|%Co8WpS;Zvt`!g zWg3qs*v7vV+<4JR=5{z`y>fMpxB>l%hk_Y73BUij%ZksQub91jksl@HlX7dXc?lnCg8|F7|2%PI$ zq9(wn{&|I<&+)%0?b-e-xBXmQbyKQkJ@bvtOEohN`w1(i1nABDe6QO6sph7KTm0sV z7#968JghDoe(2=26_1zg-QaQ~eU`FuOOD^?Y^yk)A!hrtlg(F|@x%NDdh5^5*fP_@l;Mf{&W%3y z(FxnTm5pjwOGhy1%KuH(STehCeLsWXnVJPmw{N&iep_uH#dcj`1-Jg&Yex+K%f$*@ z%{Z3vBM(07$%MO2OC$Mot|vpT*$nz2r=o^3&ef^O#-h1BZFL66SgTdHEb?VZ;o zS0ziX-`|8**iG21WN7GKJU>(EUu4}vmi%0v4V&s%a@F#|CxDj&=X_B+DNO2Ny0*ki32*Y0g4l zoii&XTdupi=q41Gy*;$#t3prN>x1pnwz)>%eqz#K(K<`@(QbkNlCiUkbeAuB5tDP2 zY5g6k9HGpkYJsAUWoKj;O!+ubm-%9-khr9}w9~quOJ#XyoC@#_S@a`eiR&+S$=H#0@!|ux7 zKAdm8h4H&|o}|DMR?8KB!Q8R``bD1BEqs1w+jD0rHxZ>9O&*V{{%Kxid(pzIFz?~5 z9-p6&=T_W`kbQh<=A^~UOFniO*ub7qSUVSbwwejdPde#l62zZQJ&Ru~t99W$&}FML#vZ-(Iv%-JY|| zt}I5Gja&xJbo6}>#zV49FJ-`aAy&Fp29@^^8C&JAii=(*V`NOyTf>x-#( zo*C+;#9#eZ+V@&eAg_@xY_W1+r>WQ~$%deOzsYMZyf+9r)VzGBp|;lSfX|ylG>=Z> z-ROMoq}uDwCF|`Lt~`*_zg}ZyJ>T3fn;$qvZmFMqH~X?+v_D^a?#`9})wF+JK7akr zQ}f~ zU-Rw_N_Axm9i$Wg+bX2TB;Pvf6Z~X<+~l3JEPl#Nb)iyz99!4_r3hkuVeqJS@_mx_O@_eCjN?w8opmJt{b{UKWL%e0Nk2l@CdrrvaAojdQ!+6&iiSzBhZu}^i|b;RGa z-feo;9i~fH%<2>NN`7qlI5)tU^SzOxc;}Ln?EiWfWS>6iI>{>e*db-%g0q|weGXkE z(GE7VrUm!yES8CS*Xbkt-)&R<;d-Xh@Yk2?ckkRW&0X^ND_c{>N6#mTYyK%|n7DiG z*_j7wX8&S-a&rRzgp7uUmt$Y`{KmLs#h9Pe820&m+1xJ-+Pa4^8U18bxp>; zA})tXE?i~u&wBowO%K`D(!=&J=@i@Mg-_l++O{XvBunbiCU&P9(=4`Ew|T==^shHM zco}SK*0NeV+s$}Kndt-mlBkW>*6muK$FqI1>if@HZw+^d&d!{EtswMuOHI{li>57M zJ(rjE`SY4s9gbJck5>8Pb)9uJ<9`Pxj$;vBepNoolIH1Wb6C3;=M*mAm48|>BT(Fh z-Eq&OUDoEBHyxf!?>+zDrbV!s+3>+?nUr4LrJsFQ?f-R9{?*BlP3Nbt3Rmd=m~^`8 z#EW;cGM4@`6Bqs>X81!kI&X&WtpnOGwpe9XId6G&i}|sp`GxpxcN@ekZ*~7X`^nup zX)jaX6<*&Ct*c2>*6j@W#j=W{#I1b32$4b%l&@LvNG4U(PwMV#^bEEs0ni`#w+iy0MXVoJmRM z|GG7D_N(`9*zjKQ_kry2BW4db|N2{9u&Sf~;OD<5ze+rw^x)Rl=J;BR!&Z+E*!z0@ z3g#1=qiB19=as^W9r4YEPU~K6jkx%GtGM>c6Z%%mzOtC*U8`c8u3Y){j&J(}z4FHk z3n!i^a4^_ssDc7@>?+Y${jIzwGSf!XP+t6_M?U!}!w_l{Q-@f-?%_c{^ z$I7o+we#U`22AtmZ_l&w?tq1v)b+4r=9N8md**C{ej^|(1i5TL-JP)q8Li80{2a|Zk7DQ zvAt=zKl_^_r-Rn#rQHw+YKZ9Vo04H{{^Hm5)a6XP>o$Z0xD;o$Z#TSuaxa_V-}J1f zZGSqlkKdhddadis)qIEbZjP6BOWVy|R;{_`^}0|K#m6=r2GfpTk1D-(aFx1AT7mW3 z#q&*Ur2=)X*WaD!Ants8!gsC}?KKNOyUyO1vHFJ1LA#_R2H*ZEK2kBsUsjxFH@LOh zPDn-EVM+UEzg;i&{dcRBCF!{EKRmy5w|?8P>fbG=Z0rln&KVpx@3z@_;^FVjQB#_j z7RtZZ5))-ff7)i=7||`fe@n!hC!sC(H&imSR%)(YJg01C>AZ**4a1ez#}|LCYu|Wk z@qvA*|6Jef-FwQ^xAE>&dw-k%EL&@Bbl)s(lq!=tdz9TmKl=_hK;<>wk??_{8k22JX8$EfS}gZvEv^IaS5?qO_XX$xO>zV&&L=qj@^zPX(?XocQ>4So&@Kx_^31Q~vvhFOk}=^IP(3!?ioNiuX6jb6LvG ztj*Jo7F$-9QglgvQ^}c1uV$z8nfody9*Dk^l6q3-!;^&%3Ld7&?);GQ+iU-!$IVlI zhr5_q-J8}m-FW@-;+yZz{kh+}*)i9~=4$wg4AZ_3$9lxn7fsG|nU~||CVX?o_tz~4 zjWeVcCl;G)^{wWg?s1-l!&iOVdXDGW-nE6ZR{v<6#^CKY`QQ0%XSclAlg(~v6WRPd z+OSD}hUatgL-y(qAlI@@|L&c?9BPe!oeGamVV{3${sy*-6W4Z3Oi6foh9&OlN27yT|7)ZU zC>ecp;9Zry=1syZSr_kISIq*o6l3L82QMy|YOVQPZyubLo^TXbI;!Bn5l^c9!A7L^(XZ-%{d{%~Mb`Lk) zYBq>$yYca;IA66a>+yYJV(&CQ2y$1cq=v{R?Ax+Mx6aOLSz>ql$&dX%zF(Ahu(HnA z#IOAK;vFpxKHlfp{lvCpO*ympeeQOnM^nGZ+Adfh8}~ow&)o+ZGOI5AkBl&O{MIS- z@>u+lj4Asb+CQK1u!qYbN`mu{wQ$5nkFB$|vf4jBroBIRN-cxolDUnnVcyyQ>z@m( zjZM7O#=hU+{JDERx6Z13GuXT}s$9WXLC(#m=t03D&h<_FxxBfBwGE;R&&>JNxko!@ z=kEP=64D>eG<1HR$(JVmWYK3&g}dvYF8{fA&slw|o%8QFIuuGnj=*&y58Z}3@@WpwldZ8Q&;}TU!1itjQ`U7N2@}` z+wU^W_2+AKZoYc|fyxZw118sJS$PPU?eL%PX@5B1O)e?;>Y~(;2$Op;0Pu_lyzrt;T zmJ7q%-}#Dqk52fgwQt;A(&QJ%b#(o{6Z(piouyxUZ#;JO*w-FAy^D`kPh?GcE%R}A z<}``BGqb;KUY>Az`iAw6*UAH;N_q>=?YQ3OlUFA1^}F)--4@=qhxyN{#gC?SINa2{ zvNo&B#?xk@3h%yCrZt<5XEFKj?9fr;%}rN6G07zUSZ~7f&Fe&8&UkaGH1^O>-83WB zu&h5vr%pWkv&eQO=Z;f?%PvJ8yFUN8nYZ&AmDZJO(-cn%G(U0qR&T{!@jETC<<+t; z2VPxI^b%Bc4D zoX`5^==USWY}sG5RD&LzRGck-HYe@)BjePdJ3J1L7v0EkTF7kk^+fp(X=R?Nkc0yw zf3JB_R_IZ_Frjwty}n-w4lPGT_6aWHU00azc5}DK)yPY4&uBe0<#;-izvOSx1cM|7 zgO^Lab&E7aN;)q1$*4SZpP&@fJpIsa(YKQVU+?zO_Ov~<%GCP)7B9Vq28R7BW!Dtf zYEGXVXy0y~e0Qb~bE>>iMBs(pTi$)q6nW(v{nSZQwQ;p*;2q;pb;}*zG$J5vbACz-EwgN$qOGNBhsJ$4^W+5QSm2!{ zI{oUtw-a^+RmK}lRXlxu_5&Y#SAiPkeTuuUOW#tLuRN<58@GIAfTk#az00|X``X9c zh2!%lJ`rXAP-fElCY8}~d+CI<*Gk|yN>DI@u+n3JRlDw{?`-9Lyuy`j?G<&WXWvZ^ zRrc*lPMhZH>t-xoo|}8;wD0m{&M@BkUtP~`H0pb-KhG!S`Czf^dg)fqweIY<*aLgg zzHlv?;m;f?eR-?viOnSoX9tv2>!0}5IOqN)ZR4-kAKB%9pMQFv!D6q#ZwKHllRw=Jb~TU6-OHL5(%&+otc#wt3=>elVM#mfUr zmaNYT+t?7})D|)4n;qZQnQ|vrl+JlrxZ(1ZLrlxPLM3DFnJ%l|^D}zI)Z9stjZgD` zD4gD(bE~&1@5+t~tYZJ`AEi6ZJhax*wkS!%;alX|+TM93y}SJ@3zuk_KYM+4?*vv~ zBh#)`vBwx@S5LXHXm-nq#G~7ci;k|G>UPL<);#`-_v%;YsOM-u5d7-SdA01C+KY7G z*NgLR@5-HXx8jJW2Cq}z4(Zh!PF5U$DLBvNSLm$SEFTq5Ywyks+Wz&4NB;V(zK4Ci z4ZUY4mlha5_eu5=lb`j_yXM_VRTkacHA-)+llQFhz4@8@&3fshjX#2pey&T~vx&_* zHU5;+HH{hH)@84Iq#tSF+$))_m>io7J+l zHjIm7Hip%7DtWtS_=mTApYHW-I+M5d!jp^7=zWQK^DtC(Uf_*2s_PS@R!XM?sMe*V z-n9HNEjvZq&*{{XKc5Y)zT7t0y^>k~!p5T?F1}Nk@IfQxL|tNbNBg^mRRRVcWg%D6 zS;~&b2=lDGnQUbn7-fX zcL49fCkHO=`?Ky%?toiQnE(=w~N?_u5hQ>?5dgf1OnElCjk_UW|K z_fyfuBF<9s#jvV0wvZ*edsY#D^ z`yHEgylM(#+~?3%9f8WPhHF!r_B*Y0H&~IgW!KgmF2*LqtfK`2QyS!(ML*0T+Pc>lvbNG72e3pPAQrCw#@H~{e?{-&LQ>gTQ@~r zZo6?>`}mIAb8b)meIVR$YG%6Y7uH|G64u4JEyhd?5i`2;gx_ZOeeT+~-;%TaZNU3o z``#b;aZUN<>!?kRKU%ijW?Omjj`_vxy~fG4vWtQmgagfX+?u!TMmgg&;quE1B_cjc zgj-GCbxUpO9$&qdy~iH?j-GYTQYSyAZ!6P=m)RnGc@2Ehhu?nDYku`^9{=_o4j(*L zKj+=HK>Yo7!Qa;N#g@n?7Cbw*+~&*zWz&MfB&R8I`^4VXxOZ(!=8Hb{eD0UfJ=(jc zy8J9v&rGh~xuWj(5$ctn^z*zC^pA78?y{7lo`ko|xN!wm)fU7Nyd z87?nz@O5Zj|Csp))54R?;pIl#WVm&OO8!YJr`$elulRb3v1P*ZyAPLNUUOd2_M2$%zqn|tZ?m^0Fs8bH z4qq+(#qf#XRj$JAlh3*Z2prOUGlBo|ix|Hpl3J}-yt2={nyT|gc#5@*v-O3)B^w_e ze>`#XXM=a|4rRF+?CJfNHs!xd3q{Q{{1<_B?qdR&>q z+ir>JM{Hk_{OZW;Pg7s-t2}QJ5K$QYS=-g2`b&@Cf7R}tv(45#+%}Cbtn$&`sWmxU z>m-hy^!&!o`oI z%$DZSR`*Ij_E^ZbV~3XX6o-S+RhDgf+z01A6!(=gB?GunSWyME_qo0GEczcz3cX7iyCpgqB-U!=C?Ko z7C2A7+H>Z>jp=zwB~7;%9#%MAo|`OJIc2fYzE`Hdby`(&UsxTLkX3qiO*>g-(Vl|U z3P0ma*WR}Jb-*Kgp4L0rnM-{S1y~#~O?Y*`$bn~Sf9>OQNqWjpG7g6E<@7iQPn%iD zy-T&&Y=YE$RlP$ALH73dv^P!^&va7p<9m7NRqO5td%mPpD|C53{ad>F-BlCqOA{-% zD(6LilgWN;t!zE({v)*?51m*RlnPF56>3_%p)KN;^sQNU<>WeFwOvkmv~9{FMX}`4S6C>wOWI)NTeTy$Nz(;?ZMo~|uKJ}nam(|~o4(Ji=oZn%`u zV`tGtIlt_pdtKo{K7EaC3!-k?$^NhT#&`O{o7~PFYvt>-f>WEXhunO-h$ZL1h6868 z8}GZeN9259Q_GX`5_~6@7f%vqP%+p7{Ir z&$261qB$?JKT+7uZvC+~TK=fhzeblC{#^p*1V zhl@XVyWL`JuTu>F5}jo1oc+vN(TY23`P#56zKoCTq($=fvhPs1>Lv5_>dKTvh2Jk) zHIJoyI@GIX9T6b_RcY=G0rpGtbHEYPXkP_%pNB-jh^U9(n)l$^lV} zljmbzDYi7O;1JnTWTY1T`27>BQ=3lSnG~hBaKXLcE8*LNHz+P<(=o7;*Jf!*^ImVU zX1?Qt$LI4NcCPgQAM;~UiO3qW!I1tMYfv^1K>y!bLsL%1-qXGoMuCRI~R_x}ScNJoMpNtMZY2*1xLtQx9Jk>t($Z zP_WkH>9;y3-@mK#efIe5;nnEaVLkg|=f8g674w^8?ek{La_*GrsE^3tsJlMzYMZ+t z(;=tW4j%8$B$lkM)>Rtd%kQ?QGo=!#4e=mpOkQO*k;~*z`_cnU6=37OuT* zvOz)6{%iQQjvf`&8>=30G6)N*yygy!TbkW;KH1*g`s&gD2@}3q36wKTSTtj%xoPU1 zUo5l4{aO5em}kDye5O3>m41<9gy5$g{wKaonYM|E$K}$2IOE%`S*|m;e6)KhHt~Q) z*89!JqKtneJosd9lK;AEJMUbtoqQ?+-y_|{9vNTOe%0fBUy{*EMMKX1>B$cr>N=mr z5^GgL6K}~dJ9aGSTo>SWI`6c0Py3VJMMafUlh(L+*LZu~Tc;ngZ$@P(UxgQYg!%Is zc?-YjPHAC?J#(em`Q?s1b)UWAl z#(6Ect?(p%{gOtv#AUZPojh=F(wwDsa~xhUv^|&Jv_#Hhj>X=<%B{yg7JI9EEy{ku zAG7#F`i!PEi`x18qE7!bG%|X5J%8VEnN$voHM^&D^!h&PDoZYIy1{yr@gD1?4>m`t zdCvWpSKq$t`k|d-pOfSzR`{J4@6fQhm2-8Yap<*EZS49`Z_A%+IKadn! zlXR$$M{)WtW$kb4MDI%+W6nOlRFBI)Z?(}yYgH6WMs7z)%yke{Cdcgo&Q$)d*sH1Ykzo`Oq5>eF7*0-`aP!4 zH4!F9)naGtw4BKL==qoLDZDxF)eVg<%$+ey-z3m4WP4|}{p)r$_nk*K-H5%rFh|a3 zNmq;4W1k1VI(-?ZE9jhM&e^W{TqxqfgUS3RznOncIMnX;&$2!(wK5`3cLD${!d@Id{a-#cU|mC zotw%2Pi5a4oin>ve7ycUpgf|r%s=RI^n+Dvw5>Ps&NfqGE|gub_FwhQ(d$pvr*1!| z(y;z}ONLbXN=L=*P89;HOEjd_R_0tx+GgzYSN`YJ9u}uFyGptRa#9aI-oLc$pvb+4 z)`wSDMvE$}V>Xs^ZB=PeQMK=tSzHlV7WC#zy1!g}_oOAfJbMeKGOvB}uf4h9(k640 z$ef><9~M7;_37v32^z{X-mGjDVEB9Xl`c=MgKqkP1()8ivR37;IHvUK3*Vzsy_Nc# z{dSf5hl)kI<{ot@s9D5Z@^PIE$MG8Hl*P=C3%ZI+)dXt3{uIb@p0VuQftq=f6YTun zAF%WL!XUA=*X7U3ry;f63QsMak5I}6YgEvew(-ITGnsoWA}0` zy*&fuL>9fU<4zRn?XKSx@YJy0(qx+TTx}n@?#*)3uP6jFM;nI6>%H1&*vfOMe&V+x zjtiSP`u^UGStFux=-&3f&x(8*wzJu2sF)w#B%qR+zj8|J^lkG>`F@-(j^1|Wyn+4% z)r0Hby|{Yu^p;>pSH2(9HKzBRR+818^)#1b-ZN`8!I(hptX+Q?#19H+oQJk98lb*a^Ea80HmiF@5oW&QGhqd%|?EbbwIAR{>J5hOwu46mOHmPJ>dbxUX z*m|c4r=Bwj->Nye+f3~Hy*=X9hXqn?_*@qD{pw|Ij!o5(j-S2iep=qTeyyxY_IUwQ z`tR>BJH2qz-NfgX*{4G}&0A#}R`0m^z3eAr@~$-k|ob#FN zrB4zL9~GE+G-$d9lf1wJ4zcB&o>$LZ`T0n0Z^gMMR;L~$|F3`XJjLghSLO6oX521Z zA$59jFSpnSwyeLE*4udJ#-#R?oRj(@E9)MGi@Y=EUcW`<`QNK$4}Gss?CqYJm*u%$ z%s47(+E$4MtFYg#hQe3Y&H0skc!8Zka&(Zi&j0Y1@TLF4nszeDK2R@@h+}SJ=Z`Zy zSXr?9ipYzFii*W*^PVXGzOKN!epPGq3HMV=ZB9td@zl00`sm;8B;xJow%n~C>`(*m zwRKfQK5P-}3%6}+JD0OQIME2-&6zH&}XiBQ`za}M*g>(zg`WTmgmo|I2={+hGA zk1yk8@?5q0yYGyDnDTnDJ}>`ad$;`THHE7T^S=lOb{tuDV9R>e8D4p5X*DlnYFb6+ z3x70A44ik%|4xk=&*~eijjo?KyJ12?IK#88Q9ehG%+deR_g5_Rq=3dX*<+mU4q5+` zn^wuRd4HF+4p+0$bmQ}8(TJFP|JnT|)@9%JG zoqZG&rqfxqFC(!+DZ~5MO2$jRS2(RSv_9WjvMglBv4$9_sKW47xhwA8 z+&yL4moM7BuT%51Zt*;3UU*8PXQzMS^hr^11sxaL=U)1{Ve`??e`du!-`+X?YTq#)Q{lBXYpVYBB>pvvzVYzKhnpRrMUL3JGF>^?d*zN| zv25udzpq!V`J42nT@cpgyMAZ$0`)Z>fpIrl5ypj`xlKr9JcXl$z#E-~8TUV-8F9PM!I$&)mzfx4N9#FEk~VQLK_ZD2Dyh z#O8DNyl?i(KV;gug0<6E-TqRs>JfDX(H9RM-;BS${xffV{>t9esl#b? zR%o&Bt7?Y6K5;*ubzBzlFWhBDwofj7UoX0{!!2Ief4A8&1(X3 zCupT4&yai4+|?bH`88#=;H59?9ysZil_VHVw|u-&r{Ur6>f*n9-`CxJ^mp&1hrg>s z{_cHvKkPu=z3m(R?)Ce3?|^;Y?7Djs89rXly?ycFq(h~r-t3&d`PkeJd)Zk(6Q%F{ z`_+8+@NI47>3K%tK8(L=dRTUy)>m3IcUDtrY0l&m=S+eMCd~R>mDF%~=Z<|n(U(-G znFa>W-z)x&t7K6@)T9hOw;k$#SmL#~0$2D7?{Z{*9NGMAB@1uCec^BX-@6|*{X2fG zJIgg^#j1V$UB5zGoyF35#ndhyW>x+xDVexqlJk4s151hox-Qg8=5lGMEwx>$Gu!i+ z@Z_WCyVNyG)}$|NeV2Peu-K{o@8i=GJlQ_YPP2U#^88R!P|)2g8&**Mr_T%u%@#9Q^pnk*7;7U%%&? z@=vMxjn|ovD+}|x8rO-5NzFEIwp*?yyQXo2!KFtpx5zxc$MWLQBN_9>8>d~_VE(6d zZlc26{dKQ?ixhJx7BQw?+&HO4Rb@qh*V3PP*N*?@YdrD!T8qZUuAsN;ChVR3!{Axy zQ4X%$2~Qqwns7|i`P#LZ9b5aD9#`GEzmQe>b=%KxP-APiT>TS=w#OZVvyD-DH zDQ&hSOVzWAvlrZj-|w3vw1oZJnNt!s-mH1!X8f&6cFF(fBfdA^JYKP+tYOQc+LLTi z<<}OKOG|wJ`(4mXCi(}&Ct`q@@&Rh#F{$>{?JWb3ji0G{N)t2~J@X7YNoXxe8G3|S=+b;6X&q~Tkb=rPphmT0F zolWnHHx{CoKBV7by?S{Kb<~WuyLB@d~r>I4HMdeZnt^g&3apQ z$=a|j_sbfstLyT8(?0CYo#7c2up!^xoW1j$@E@T}Z6$g6o8~iT@xD81n{uH*INiU! zUh(Ft*bOUhWV77-q|*>RZ_lB7^4d4=araF9R+aI-+xG71k-hxl9Zk+Tp{lanlZ8k?oiEe7yjVRMX9!PZA#PF)rl)HRr0l+cx`Ef0kUlw!$S@&cvHt z-d}$m1KXpSCnSRxR2ghj?}~ML&{81n`jks~#~t~53wA1L3znSu5G?)qkDgCgjLcNW z1M4QFG2Bfv{&pgWSx4&kAGz)Br;>K8y`G*pJ9Ky0yRfMe-1(oaZiEKQmezD$>QX`}%aDrQe>e(NAnqme_N!qi&kkvc(xEnS75;u}!?6 zW>WjxZGTt9^6!%AO-jn=J~)V}U2?5|60F3Ncyea{>x+tq-8geYWgSjFxAI`s74oW6 z?BMU+)}%IDCgN1ckt6+o{yj2COt?GE!{zw;*t@fPuRVAEr{Jq99iF=4ht{?~CW}+b zGM;zH=DP@nUOaIBfMe3LO^yexC;ek+m)_04@4E6|>jSH{%q+W;GFeA7P)%{?hHpXP zeZPPG+PBDWQu~Kv$Dd5iS+ed@#Yto*6(@SG`p-q^dcTE@;v=I)NrlH`i8Y{1k;_Ir~L>z`u$o-=!O$+ z7Khy=eE+MznBnY`vQzwNOZkp`&eBy+w%F>l7)csbB(WwPT(?-^MC1!8#!tS}51xG* zVbrq1T9jSh=if@CV@WN1H$>@ zY~uMX3%E{x{#GmWwONPlfm_eatV^}6?-tjrpRsTLt`qC#ws@VDN?Mg=<{{}X`$#q> z?$Cn`T^+H97tQ){JCDh4kNx4_%`ZPL5a@VtCi&&b$u@nBTaR!X-dEpkoW&EU=W>4Rnaib*5y^zgY zJyfgPWBzI-q{It6VYJQmQ>=9kQ!lB3K=jQ%Mt*ZYSL6!^UEB4j} zU9Ys=D8>J#$NuZIqqSen9^U5JP~e!o?1{Q;lIo{zK8pn$Ckv_yJ_>v2DlIbK@c7eX zb7rUQQcBg-Y`>iJd^z7~zQ@aEicT|#5SZwC=j2Lr$s7N5zutb@cP#(u(ah6p=Wjg5 zo_W_kMKM2BTC=GC@x*7!f=1=%rXKI;%TqfoWbyZ7kx|&`Ih|D;2Vz$~E;zyN_~>Wl zR^7{7A|CxtJ@3W2F8S2oa!9!|ajEs*T2`GsNj6)!LMNYmz38i0fpU+&_ugLjGPk7x z+x9#QZD&-`tdoE3cSLOCn&w;UU3}d>3TUJ@%6Ey(xUMk2=cshOqfkfj%xvwq4~iAa zHd_`vyjy#K>yrb|1ieDF9V-K!o#Q2rRp0IrUhSBt=3O$uqDbxC`c+wn|M1vfRze-`A^@Oj!cZFi#{O0Agey6agQ1g+`?yFh4ds|(U zF3ot>{c?dts$x_092lc~jb9~Z7z`e@0e-Ax~D0!|#+tXF@(I>z(m${_JQB6XaL%B&@iE);oP zJkhbL{>p6OYq!I{q=;&MeRtw*;O^H??}}&To_V;#tG_E+VyP|b-qZ`V#`%wX&dtBQ zD9tdg;HFvLB8|?oVJe2>b zel1yS*D~9$SxWl+e{ME!IWwc_`px=PJ+53SQ`f9MEVE$>3%9XSnbp0*^Cqo!bFa(n zE3;@>@O6E^U01)^waRn>k!}1IQyJcD5|(1V^=J8S)|3yd+2^a)AMNOVy*~Y0)b=H( za|F5#jFYR(V_wZ-ov^*mR)X6x=t(@wE1hnuiD}o)u9$iCYk%7PHwn*X_2;rKk^Xn| zVqCPRqG;#FMkoCP_iiROhgL?FTrW5~w>7Zp&$)>HfU4B&JDoeNOIETR_fgOfIeBx{ zy@kzrF;3am2h~r{zV>!j-N7uorZ~%j^=IcA*e+7}EVK7iV9>6g=I7$Kx|~X>iPhFy z85-eu@Xl$Iy>%~5EvIswRPS4rAR-+6^YNJ{k5=rP!`OV3pS6nba`cQR%xfkwg-i+X zJu1%h&BgS(eQ&vtz4kK8zk4@4nBG+9IP3Ei=RXS4zXHxJ0Uav^KEMccnhhM6eVi8Q zQn^t$R=3T2=BF!5PfzDM$CbS7O5671+PiZ6Cp}?N;n?-|#q3w}S1Z2G^*>zqF+HS9 zCt>E2W=-kEsW%xvNxt4Qy?sHCypZRthV8lM`s`Qyp0r`po)v``_TOD~r|oM{+Khca zn(p^~?=9wV_qiM-`O5O{n+t7!R>f5N=$|a?JG_aDbC0Za_?B7!K6@}-xgXVIBl&!p zAD3jzLp$$e8wS@IsTDULJ8oB*1U<1G&QN3E6WLtdFoTtC5`XK&p!~;D*+1-RTBPnT zY3EyQr6C=CzMbj%+mQB7>AUX&td_jmsml>O^|^)n;yWcZGB25q@k~{l$6~VaHP_6R zX3-DoiNJw({P@uz1m`{Fu+4oM9A7IOm)>pjXT!sy`so>hTQ`ayZeoOaiqv9u;dcM}oE?$39;;Qhj%A!O0XY;n+3JAzO33n@uk?SLI%tcA! z+ZB~|)s3>@{j+OcAG_GNw{u=}z<%f5!G+5EgClQlJ84+Kp3z`r(U zO_^HEf1WL#VKNDZb4mq89_lmM867uL3ip3GF+^|z^P=;cYnuvob5!uv+cXt2uDNFY zU*gtUZKha(Cw`y2#6wTr+T!E!?}gg(kSEcHlo*d}eZEyhS2*;v>6K~PH>D5jZQ6hS z?Vf)J7oA8BDNS)TG{_16(P5(hC!lX<3H!`{PB~si6Dl@d>ahQFvs=)&-7;s*Z`R1R zt#%>@dUXXPlB6bCM_*mIcgtq?J$HpZoJ%a7Z5`mRygH>g?*Q-lcwL5?s=yKn8< zt{U@5&2_z?y@kllw`(`AUCF#pcK)QG2d`T@yDcZ~xz)|Gqh4ZN{N5;Qd*8MlTSeq# zC*PjzcH`cHJ6*=fOG7hza%XRyQ$Bgw#VgzHPgDyyy@mg=WDom{EW2cZlt)Hf59*{m zKL4JzbC3B4`&Pz4gfR>_`B%d%7E)qCTy1XPW|clsN9@l`QGAm-+H@u%`>y- z6v?J9?+`xku(ZwOfWi5HDmy;hTpc^}rGfejEo+lUANfx(UUOZ%naK*4NKg{GGSiQL zqCa=@Ua;Hkofx-eA7@)|f#v_aM+M*W3(eFv6<9u2iL`$|IdzH7oR1-z%U%W_J7<5l zVz*xY`Ng^=7vywf7@}?M&ROi_eX#L|iXzW$lfO~snma0^JGUJyKB`$c?fRau9dSpW zoLX+?e77rfLB+o(hl;(Foo4MdYrO2g@cADdwckw3I)iL1t9O3oOIa+@rg-EH|I|mh z2X`KxH;YH0bng4oTg|IgwrBCTyn3eal8bjIhBrJ?&Ssix-89}U6xgxyL;0^a+ZrF| zP4H5a{uA?pBVx&~NfXPBZusz=ovYNGpnJC2k$cvzDJP~WTv;Ceedqc~`zBoem|Ag1 zZ^O=_y*GbvJoxMNwx%4@|B0X0=a^JIf4_E$t3~>x7QW0sm)9kmeg6FNzVTbviCYf$ zzZB$+Tl9s0?r#J4f3bSTzBS4Ii>E|#7I=q?U)npbj5&YjW0hx`;&T;lS=r~m?3vL$ zceaAr6O2+ug}d?eyG;&HW}A4%3$5sz);;6l6}vhVP@ z^n(X}89X^#&da+|oh^WaanT>W9&dN%-muA={7pH6__=qKod$PRz6@5_@F6l`=K860_Ywr!`|fq(JFF4ld~&f7$EZc6{#b;Wh6 zc6M?6!&BC8WRoOS7T(?<^m6xue66Q`+qDlQC?1=;OWp=;bmDrOkoCCF!g#@qXNMvJnU*bkAJTY2 z^}+oAb-dm@oQ0<|r}IpHskrTn+ezu{46RkUCIKts6E209{0&&YqEhmPTWnmx;uG8B zRJ3iEn_7IIyJ4HcQ6A%Y$Bw_Xle5_)DZ1m5+Qft(ieaMjZUzg>hkW|R_^apLoK&4Q z?_h7oPl4AkOH)skDGJNd_f5Xmt{#x~>rdh5FY)gCRxvf1ZoHdyc{TfiMJ_F8qmwsj zzSfTum9>lOf4DFH-`AT9vU~o@N|#9&onrla`t{BW`#a*le*LD!BCp@ZEVA80W&i); zb)P>JYm{|}+s-;uW#{{eNp8-~&Ldj`^Q_p$C@7peKPG)JD(VJXzkON zOa27PZ2e#V;X2=)aGodI1l&R3FFg2PZ>Yh!|8()u&&g}E`e)x0 zSt)9#yyu&=#9`B2XCCjm{b)zw z3)w79dmNQCfKeQ9zo#xS{wP(h9t;^1SRy$q`T5=rOY`5)U&Y#Y; zdV&AWVit3rDw7lYUaYG>_c1 zf{)?wC+-RZb`1R$Cve&guQ7fDm1*SZ5`P|&;5~VV$=H-lKf0rJq5mI|uwAgmf#+GdT82{dV z2UEY+KAkCL5sod39 z!zpmiil-)hPbxU8G7LS>Xa0T>(w(uX&FFUdw);6D`8$edr<#^Ta3w!tSkZeY?%LBy z$|oM0R$WU}@(oq<op-JWURR8k{^NM5KH#`)W4&y`yqiYU&FLJ{Hf}32kiWtcg}Ei_QlE{w!5+&uXUzW`7A(EMKdRvU)$NmJD$bqp zd!d!DaKGwn-Gm3n4p%-mbyaJ=!!%{<^0OO0Iy}{1Qv2}(OZ9D&<{J_hWo!;v>MCwy zs}O9M(?0KePItiL-bHuM&SEZL&N`{G!ti3J{>HEJ?rJ9!LT+iD6_`2MiD}7Wo*0*K zXXS!hb%vUI9N#3}o%_9Nf}-G^r3=z4#Dw;{$G)55_jCUHd2M|=LM9$4{s;h$`g(0@=i4>L3w$4&)Y97UTjeH|f3#v|$xbPZ&yv)8KV`@JdCLUi19#M@rsSPv zRk?XRF5=>*rQ4LN&P>)ly3oRN$;*GtzdpuK_+PTRIm|a+Bk{jw=_A&#sH-=BpBLEg zC)9uR*n=yxQ{*p&zFlp2UAn~W*6ErTYl7{cEmL~3G})$qqQWk%{_E-gb#pdN-M8Ie zg)ez}`wqFwyJi@gFg;toRc}S<*Ga5Aj%{kuhch}VGVh$K41X?eK8IzIvi{Qd$LxA` zbJ^@YspRPtvgCJDs^!@;=c;Qjnk$}?exPL3r5Tefa-sKy(E_nA2HpFE7o`YC_7ylY za;;+sRPD0UVPD-UaQsBaf%b*59vrofn`g$QYZlem#NECw!=m#$_2}oaK;MO7JQp^H z`oEYoW66}zT{*{AR|olqMa^e;rFgg|>rh_!Ps?K^f@fLVa<1-rrFLumgLd^#!RLkj zcprxUXkGBjeOub}j(~a7d)Ht7XgKpifqy!O?k)KyoA&yP3zs}9{a=0Xn@id$jt+iB z1>>BbKZ8C8%FWc=(EsGhgt9HXcfAri-cEX{==3Jw^MSCNt4+5})1BuZSR*#YxaWqO zPMG&2=i{5VTU1uxx1YS*=ub0uc;+IML->2QoC)lR-l}qXjUGiP;-YjE_ z_+ZmUmC6ONvb+z}bLs=8yDxw6Li)`eW{2}_JqISWH6L`4pI_33mtw&F)rY_xK{AUM`fy_(Mh7D6?wiixJc8gfO zDAA!K+Prc})uA=ZR;l}4_U@R<860I@-h4o===Ta^?fY&Tr7SmCx+Ghah0h(XUvD#e zk?4}j&talM=^STtjJoYN&uTMXeX4rDxYHvyp~9vkSN1)+tK)LwM}m3&i;D&Kn+$mW z>(7etxuVmVHS6fa`tug5b2$Dn2mV*S`yg}UQMTaTBYM$y?!G}acl0+NTXHog;o3`{oLSrgd4Jt}kH0@zbw+ZV?#~7xO>{tAA7Hi+UyC$!_9PB60Xctsj;L$r}~ zU%9rbO1AFq+?^iDU-qihJQI)MJ-~V4bo|fey}SzLr^~)R@Z24~z9D~ZnXKe(Iop7l zmAj*zmd?L_<=^4A-8DH?#ZpsNsl+|IDB{KTA$ZMK+n-+y#8`H{h<@-lwzY2Eo&VZd z`@^GW*oVj3uijFBb!GkPl{WgTS9ZU8!}e>-_HSQi$a9PRdA=h0mFma-Z`qx<9B-L_ zoWI?RnRWa7Yp*9*TSc$$oaMY?`_jYH{O?cbH1WCg%zf_4q#CA&mlt1l+qol_ebHM^ ziJyCy#r~1Y@>(KXIDwK z*zn2i&!&n?rOQ5UzILrs(9Ox`UD&MSd(4=bo=IPs>GN2><<@upOpa|&mufK_`1A0z z)YgJm;Yt_H)2DGp1i$er+^ENS`rAo?MsJ&)`P;u|i>?1O$0>68?y^+=Z_O*V)f#oB zW~D5S<6!pWae3lk@GWhnr+?Tg?vx9kPJC6_b|*b#&u*wd`bd) z%`L^%y!*T3rkww??bG5ZVjtCalysZDQ8H9w`+dDx=ELRa$kW@OS*+b)@Z!VOUrkaB zCLQYYyK*MpZEWS4@1^n6Qbu{pgni3*yt=u4xwy=fs{L{%>Z{)=&DFVb)gXDB&FiY2 zc?l=qpWT@;$%gA(u+N@Utx4R;8|Ns8Ju|y9@v$BE)GXb|g*C2Rs*|qG*|hg><88|o zlO4PbTrY?^&-?0L^IDVk zn!8RLe>O5m)RX%uvg+ukv>&fFJiSu9=hUInRH0b&wEYTm7>>6Ety9t1|4`x4zhxWb z%Wc;bMh0_-rSip_o#DO_-})oIRZ5OKf_2glN%^7)GXwuNm-#aNFcO)svqXmN*TuyO zA40UFb=N%JdvB$s!E42a-QQEvEJOZ_ioDSgD-``)A3dqF&1_v{#H2+UPtMM*GF>{c zjZtE5?qla)f>UaA6E>yo(cV01(~{MNuNp3DtTy+N>qn0x0xu$~Z{+2+ORkEE%J|ErvL?|&Zn1ZMm9e1e z317u_UXGuP-*%J~&yxK4C{l37y>o(&UaGc=M<0nNtX8vMeSFvC6)o+-EUSKpOIhsyxeiC(isgWndl@ zEVIkpzUlC!9p{|I!j`OW*G6H>sxs%rg=V) zbbENz=6Fx?Cx9wKtwY!olq@>e2|HA3N!cnTcUsu+zPw4HPbj19e zQiPk$;eFO!qMc1YG`N5KW{*o^TeZHa|M`UzK0kf`>ISq#=IXO&g3JXx5|tmz5_Z8f>2&y-+i)yV18oto6d)=)$0zm8D5yN90~V^xJT=@_lp~>l>Y? z2Cn~tIe2xPx4y5sxy$f>rJ6ysRF7HZpS`WT9ij6%x*zPjC3vqm@5iklrn4)j%PhOo zzM$Xp_PZqUnpxJbq}RXS`s+#rq43y+f|6xfbz!_EOm^e|N(k z>5b))S8ZC)&EFvz(+qJirg$dW2Kd-pB$%gH@!3w2#Mw7GZr3n`@zbAa=X@2TAVXF6=j&AvX zoUyZ0*&L>^3YlBdAnVg2Fnz~^bRF?G=gluH=e>09cH`Vrjhipblxgsat)DGC zSIc_}Z^|KE_WbSF%lZR~Ywr{Y=nbf@_yu`6i@uhYAm0!7grW|?MxVc$n+XaC? z){?E$%;z+1bTa$wY`o24a$%as#_j(S)_Fy&I5szV&cYPGa>pLQN#T<`p4Z4)%%zYzrrL~k* z>x8I(GQ73@{>fQPZ*8}+@Ol-p3It9$^X<;^?^Baa%;%I^ubBGy&1K{DE%A1af5iGC zm#8pH2JF~$`{MNkzI$QpnWgLU-DS=z)haN>9KJc(f>B0;rRDO>=u!7;)>z3Z0IPi7rhC zm)$sT?mDa1>&uKMcl-|RShuvXv@Ps~%;n8h`VL!z3yN2r*rUfB)b{pQ(utIshYk!( z2Nr)SKXH84uJ30T1=i1%sylEqll_glUXn$6k*mr@m!8je%Sg-+ZU>_ezw8o+mfrcpI9$v`m-TyrI61Im3i^MSU6lV zPA%_fKW&(!$D+Df4>T@zkEQCwQF#bm2p>Fm_mlhY?_ z>|WM>>c^f*@yx5tK2Mv|@MGO>@%sJQIktw3wd$g=P1C<#_;=p*ZyC?8&i%8#Sx(t^ zLV6y**KQrd+1eG!`^DeMc)!@q?{Xufr_QrrQBs5*nX#`IF>sX>O-|CDeUtyv>;)%Z%??_fv+0qbCC_;S zyDxhLG^|dDx~^PwV^1uv=ch*#1dY_0pI!^k{CzO%;G?_cK~@2wn;%cV$+UQBZ{f$u z9?v(lRj;q>ab&SGyCG2X=hHgg)VN@QwwKX4OXjR;4EM>%TBo1bDey6V*3+&FO=%5c zZ!lt*e2-zb<(6>TetQ#w*Tw@S3UP%{-^$!%>yoVks^)c%+{^H6<8fY#1HO@ z-x@ii!TVgnxA*_+KkZ4MJS*|thFbB{%bl9uy8cd0wOz>e)^zgF{c@_GELJ~5LyMYM0GmBTyVLrwLkIwgJ_gzQrrUJ?@9@UEkGDx@C>_#-+)=pRq3Ubi8cFnH2jaH%z!o>Q>(W z^9bv7-Icp?dLjrqhhbE{SO{botKf$TLagWKMa{&B$L>8 zB70HTp5i|rdiw*|7k2D9u4!-^bCzzB8F%S?-*Y#bMLRFVmOt%T9WI&r`re<$UcH?a z$6PsXC;4vc{AC)&!Elwuu(>Py+?>~cpFWm7$<)esJcP}?Z+ZS~x5EjMZ+H7DAN8;~ zyk>FQy|*U{~ zpZZ+t|7RyD6j}tzbT3zST)1EDQN$f);o~3g=hyacTYC9R>a;7LeN=xO{~NBVJR7_Y zW7>+8E!mGR3T#~}r<$_sh}PNvGB=!eUC31OGP~|nzV6~}OaFWR9o4!E+`N`CR;g_G zz|6_X7N|S5N6H!(<`sq#UW7b2=*Z1;j-a91Go$y)l{^a{J z4=pQcdchtxu`=%FzwPe|=IjormfmrCTZ`d@Z6+V<5*YVfPttZiI@R@S>J!Dwzl=hf z{+0f;O>^7rxz%B=K&?#S8)4I5vwm;X*>+)9{fwQQeY#CYX6VZ$t>N$Ym|G^hp{#%J z&%7Srj5{-OUustApO3u~a(v2a(_`t&_AHdonp}GR*M0>P&gBY=#dB+BJbwHoLcif* zXkggxHxu|`^d898+FWL5*SQ`)<-5UQmHGAI-K(+^;AUEi;nMy7LJ|Jh}WFneBk?P<=Lugjdf zz}bmqqgLH}iwTLmssXRX63xDMOzeMB^LP4+b91GGekA|$H zcx#qPxwmFDdv8|}VLQg#_&F_9L{w$^=bxv!-g_x-VLmvabj=J7tGy~upWQCnWU8oj zmpO`0izQIc!FY0iq#?DVF>&Wg?n!|j`V(Nqh z^?l~wyPmG@ar$5X{M3!$HDWryens3od{_R}yVPAGIggjFGmUx2%_%A{V`;wAo~3&v z_MGtl@BTJ`J#wPp=Z1Yc`pt z&zgMHwsc+TEIucfeJ6~36L~u-*gl@uNZhgidiJxzjhVa#g;~=x7(#6)&N^9U zwfoFQwNLjCGRJtz_QzR<97{g9AUlpTf@|~bzdSE=jvXoq{`RiMc%#pLWK(GRkM5*HE< zeq7$V^G&tN)8^gR^mJMO2WFj6iIDM2&)%*0E%zJ8?+4YEUvggi?DT)@&CIR&zy8$q z)P>=pbA0w~jQjpy`gv+^!7Ee=5)`Zr;hC3l|(b#VgSIQZD?^?#rpy zu1s&*(A{2kiQ9=qyhLNELB_B95*=kt$L}&!uf1bp>>d2=#LkWjx>Eb*|1UpfsG%TS zY&G+hU##nv7bZKhRs;q~ubJc5^>NqirN@mU6R+RMc3yUWcJkL(T0Pxna~c&yJk$yt zuV;P_pD9>qYSvH`kyu+V%@ShH5k%kDSQw*d~7Zc;o}W|2l3Do*i1q*4r2B z>EjUD;*#UEe>!9NwZtznzfOIU3$)*NR%q5{o*Nab)IM*PH<9oz%-W|Y=hO6a)dbns zThFf-c(TDwKkU-K!p@7TwY+uzqgXb_$eo`e%027yvD2PXMKZsSmkKtswxvWIIGyQs zLR#Xkub!Z#?t~R=9NT(ZzVBx%Gc|hqL}+!N(m912O7#otj!f}h zWKk@^QF?sUs?eqFaS19S*EU<94Krt1@!-}=wrB_K-tX~|9RK~4yS{AcUHq==*$V^r z%^!O+=CHZj@2%e?T*}n{aBIKw&+{8qLxUD(*(M*meCx@C=vKq{lU^^x#d6D*mzJN| zyoLS2e#6{;gVQpH8n>Oku=~gv#XezK7EjZ|_PcKW`SVxS(5~iIz^Yt*zd(U^oDAE1 zSM7cu@P3D%EmhiP`ho{Y@&{?@hPpDsITP{ARmzOZ35sRqE4y#GgOif9b`Q0K;XIRqh&zgiLz< zZKmy9Cli+q-_L$oX|ySOt@XMeWuadCVhayUS3UVIsqECvjhpspT$26sTWW&O`|ZoV z3L3fp5a&9pA$^mF@$0rgv)b1w^LIOJ-phRXe(Ha9`-p3bY;n^c#WMXiAn5HJ|M)wv&iPxp4I-|p62O_2RvE> zJFoRHN-DFw*4}YwhtTfmB@tcvZl*PJ6Ld<=9&SB3Ge$-4*wMJ1i=-MRdHr~Dx?FT#sveq)J@Be+j zeYu|Tlml|%nK2iqb^DxeV0zFWx$VmS3vplnZ2iH0X!hGpK5P$9x&18G@zAfi&3XCW z6yA2TV%@K&KEE^G^*UPiiuMKL_ohyYo!*CSBK&oJp0Ba`Dg7c`D@m=rLRhxyJWIy2 z^|3vDH&U2ST-2L>ej*q6>x3oF2Hm;yL`9e^z~}qEDB{N|~>Z zUxYUaXfL|d(_I`?d|AamjP>4~Wm`U0ZO~2^NMbDr+t%)T%cqsqE$79?e;JLpJ*MhD ztlqQGnIjyKFm7udC~Wa=fU1y`RduznJIW|KGL`$f$P)*o_@jxwaMOPcmnySh!;R(idz z;X>A;^*rxVy(4xjq@L}4q~lq=vsgJltf5BMPbGZ+=MKGhRZFhF%a+^3$8Nhlx7X&` zUrEgaSDx-a-7Il?`qOo<4ZCuxPdr&r`7@Q{`HlkzxW3e!+M*|UfBuII8?k2bbY{sp zA2>VNXWvpYn>N8D*R1`e_+=v#CgW$?g(nK7DztZWy2>wL>btR1=k!Tth53=I9m`Y= zv$#6>Lcx53I7gjvCQHK=@@1^t>bYAbRTJn;@?c@x2W%2msO!&*aT z>4D6I?{BwTKDzs_Buv+$IIMiq>reN^j^FFlNRMh^))X&`IUMm@%-g4}0 za5Gzf;HKgO#SN_0s=G}2O+FvkSG`G_vG^`y)6&Aqz?m0w?`W;|edBU=|CWQ3nWEbs zJ+0PS{@1ZEGgTsNN@stNPr8mx(XoA#PCj|c(lW=lJH~5m_(R6*ZeNw81+hCq7F#X! zds}~dc2CXvs^t;iAH>XSc3CY{;d|z6sNBNmv3b95YUy|vv=u4t{jn%hNy2@vJ#+5) zg*V(qGsJHdYX7=cU6%U9dG{%srB`*ry7ygK6!UgsX5-SD)+2=mN8L0dlmy&`#2Jf( zw7y605C4#Fd3=(FZt?NQ!7ZW^!V2}dSJQdQPA^)TJ<(~Z_p*I^J_P%{nD*&UUHqh@ zHcul|9y<(WnEX*f^}6@fN8V2w9`w%+tcj4@vpW3eX`$~EKX5j#GMQq($;zF1 zj?9zb9Y5R7T%63xbMyOIehH4rOcy@P-k0O~r)@byzU9w%&gHC=4~bNW zv|1&_eN9jv-;C#ne{D*TSyy+%BQ{g$=hUdU#gc9Z8>e>jO__4-*W!cM@+uF$T$XtK z)O2?9iOP?hbiFIhwX0kIJgEDhqVP;Lr8xW8GiCJ~k$*C4u6_%+YW>YDsO}xv-$} zPG{hZ?Ea0}#(URSnME=B#=UKL6Wlble5vlAg;(+f_CAlO`?~e`g?k_O-4t+M+O=M% zq3`>%Z?YX>}wb{RsR0>BVd@)8gx=OewggbjdE$`M&#=WVa{X!U9$W2EMm@ z)cal^jNZHZQrZoTOUe)1winyZOMd6dRP)O(;%(y#KUK}-fFC#Jw()u_nr-t(L@k*u zbbdt4A)Tqbyll_U`u2P&RY>wSz^>w3(0LU9Lvel-q2a zwkJGqy!riXbH3E=LkHgQ7iqDqPh<^0HAC{6B;UEDkD=L_5A4-b_N(Xr7cQ;yJDDKg ze(nC@H^(2o)<66zjQLaP?N4RPKV|iwyt?4y4&GD?b4PaDsh-l+)jdZ|l~*1w3{qeE zU3>Qb+Wj|DPn=9!yN#nOELiK?kv>b!Nf*1V6{6jy>0MIW_GDLoWn*e?gJ){KHmj)6 z2Z88K8~%QYyq=p>V(xt5p$>D@wIZ4lV3df?T0lWV`6G~aRFc-qnK{cP7(>V{r!J8yf{ zb`J9^SDRxGmK{0tJo@)Ko=4kr3g3vVtxEXtp)*5em6mzz>KE&!cW)JTV0)&stk{mT z_1bjvPw#!_^WIypBBYaZwCmvm5w(as){ykpEf3evo9Ap4^xk33KTh`` z$9&LJFv{m^S=7!$_njQiYwCY3tGblKb7P(DvK0)8(mN&!>o4E7#N1LlT)<#LeKp%F zmK<^Jx7%wL9pJj>+N`~A?aw&(D~)!Hi<9#{?qp;sD*1db^}-_gKy~ZmS9~R2bMk(l z;DAT<2QPuiEAORei@-@4%^DD|-q8X6l3$C+RvqUtIe0Rgcli#q7ax zrC!tbzT|&$Ide0^*_iScvFHRH>+5yhwGF7`Rc9?UT~#B+|pmQ z+D-Z{8&e(Eu}Q15<3IknGvC7XygHXx`njGQUiDWCe7NJ4)*q`&KP_R)aazz$->Gkc zocWmyVWIMmbN2JRUVbp>Kj+GfW11(Pzm0GGG+$L*+IX+LjmX2_I}|o>MYsK^cyHoq zZ921Oj_E~#$O&)6Dvqya;0w1i(cF72Ywg4$p?uTjttLO!@>V6^{@-|U`l8K$yV|Fk z8U&j%RLfW#&is7Oeb4MERcT&zNgqyaI&IN(F=n>$h6E;$9ZnseBl+H4n*6jVNlEw6 zp%oA3`%bW^=<-f=j$g9o#JyD(Pd+4jKcB?@?!>mYArtgj{&Bu7`Z^=%({fu6?)V1` zMqZ8=W3l4 z6=c7YJ@j0~ydwK2Y+D8H2=D!PaoPlqf(HkL?k-Z6oG0w&Hf6)ypT03S=K7s8UiYOo z$oYc6n!a4iH4(YWo^8*U*!RqS5Wa(dwGo{0{Il$` zH>CYgdS#O3tI;VUoVno8H~A?uj!!hx{ITG`lewI$g#Khs&0HeA_t9ES24fQ;8Qxp# z+~+BC%;*xBxsS!-UJLV2i+K+@m%7U|tO(8D_ey(Ny2s5fr@SV1Klk;#rFRkv_-9^@ zpV;~Ej#aMfj?MqW9W#Pja}F%f*j;%=bkU{sR*Vv~{<4>^;gH>CzS|5u#fE$A$YxgaKQ6tRwgvp(} z{4FITJnr{lv&{YfT$ToyKFi3CQDwi_ExJs2!5Q~eOLsK;^Q$ilXmw7W+xUH6ie>I0 zCjF!{{_DROJ@9o}S)8P)w>HB6vsGTUX~KQJ;<$dLN0R&hb5$Hk{&!iAz4SK6v?j@} zteb7?&9A#Y($#34@aD}8&yU-GZQIj(?w#%Ssok;8LDT!i7R1bt-On4ta#zjK?4Xoz zZ&JyRNdf}@1=jN3{5jV>dHwCYh3zR3jB^6l|NARjCMhZ}$a|^yV&XaBgqB_tkH5@w zZhzhSEMl|9oKo545?>=?8Rly%o_~6BaY~8A@^j^H9zTeMZ2hY_crg3E(S`u4g{;o+ zn44S;d6GVhvUHoKpDvgw)D^VHuw%0Fw6!ZO1j-6diHO9!KKM1aEN5NMJvqnHS(8HK z9dBjp?0Bllnq;5pYB#S zj8T}l#$-zFhf7W@wIYtSk56qn{Ha7@&%ys0VKe`&DOY-`#by;UOo_?mO~ZvB4iZu^Pv z15f1j^!YlqB{@ucWkZV={;IoNc520?eanu1-yZF_<01i z0XMFxYAfp*gc!CiD7?L?PUXlo&eW&(A59lnJn7Q=V}CSmH-`VNC{ez;N=G`J{lfiM z{L70sDF`Kq9$N0;`%lf%wPsb$?Mwck?Urwnw8dvuJm$Q2p>6irMS6W1@5FxIPmXl$ z+qt@Bvh;KDjeTClB8@hYORL$lzTU|=A{|)~^)G7e(!wPb%9mHqTDp3^WX_jc(=*uQ zHuAnbzu4_r%ng36w=Ms|#ZYWn1@eMw5Ad^)@EndOa`s#NwivDc=&} zRz9Egm4j!MY#wK{cckji(5a>y`3&>Iswa7AyfaHqIJNfpoJXpQ~q<937%KZ8<;9=n&I3dinjk zZo%TeK9+WjRd3!blH`3) z&8JpR{)BBQu=>1o$+5^{mt?2-4<{B(OVF;K+tqz$NA8La$G(u`tShW+FKpuftnjq* zs$5^|%GkP<`<5l=Ps`hw-unHe#%>*5kGRO%+5EEmqgJ@g*dSk$`K~x)&$+Z4h5Wrm z%D4YGzi@MGIN~h$Xzo(Mgq{=4ulSj_6a^eoxE6h=q~m#1jB~rx|ERdbI|{PJMK9hz z?Yx!0C-7q3yY~v~X7AytzP(Ld!KPNCtiSJG?b8TD#%D#Un(c?v-XEL4@R@06oZGRj zI*X0dU*GDOxS(EbcBk@|#~*Beb55}8Fu%{bK;nf{r@LQbw!OrWLx1zk?>ye8@b-nL z@e?MCjRy**ALDu9=y3m02K$1k3iCy`wMd%o7GG|!s^hXTzmnGcQ{<|=``!IH95|YnwgFoCRD_3+&fv+L|*xGP}=R{4JS?dXLt5K z-!=DYUVdR+MNO}ipW#HcjG#3EqF!vpPAUc*Hjg&NGj_~y>i8<4qW{}FQhY^^$LY{d zt2!A{*WVAH{cyf;WQ)1mwI^TS+uhbL(c1WU_U^wArVI4MB)bIYFos#o3DdAm%J8;a z^kh9pFo)-il!-#k=lL(>*E@6FnHVP|b$j6>MXf)RjMTYW{mr&acyA?_%psJx<5%5d zv4VMv_1`{yV|OF#SD_B;-VpWLGlko&l?%=pWc-=e>)N&VEI)0rPj0J$kCxXoyWLBV*FWYu61Z;pud_>pds?04RvAD1&v#*u*5cbs z9eb`e%;~ke_Ic(hUyGO5H5W%uuoIH%FS${v!3B+vA%z#OjuAK z)8jYkikj@Vx9xB_QpEr3@??R2>$ySu^EN4|DBf(D@z0s3cwd9lM%mCk<>ikJ-@+|7micU%_cvj)U{q>y%fVS! zIsFpnR74-NS8ubwX1l*~{~N0R>Pr2U z%40R})6Z@HyYBvWpZ)m>_1m}ae)ay@uU$R&S62P}?fCEBmE`@=ouSFhy1dcFSDz4)(Mb!(&kYp=3?e*5~l;ID!4tN!jQe6ZPC zVEOwqA)Z%eYQ>yB7Ph=1@4BY_!+s$*RwkaMZn`^edb`JlY|QwQ)FOJE=@@9;Po4Nf zB=q3N^e-Mi!sTu}pE0ZLR|50&Qn~XjFPsneU9)4{G)qS;^T>{a+w2a$S$OJ)0{;dN z)2UhA6Yd!Q_sg!!{F%8oDSW{MH~y$6TOEG~qDrGGEokEdskxVLIeVqY;{6FRGcyIpq)MvWY$)7IgZl@&^m2ynb1^bkh^Fxka)1Ij>Hb$8Iz4+i&>k z&_T!6AIHu=usqK^$2Y^ib6QL4WzVQ1oBpMFH|>gQ$*Vl3SAG2;=kEJ9W=0R!gjyEx zFX2y8c>F>jKTfUow!Z4hS(6u}_GjGLzVyXGzQXU3y_V|Mo5EDDUS2Q0l6~Tu^n&YW zb<49v7OA{7;ygC*b@)wF7N&EXSDmWQTzX3H%be9yF5Oo(>zkOu@i6|A=2airtBei4 z)k?)d*Y{qNc68(4vu>gL6`dEol5ZYUdQz3rId_ZT z<%K7M`JFqy&NGuO`)^@W{$|P_NuSHb_nzLj&ieO9DDotGUCQcxVKGO9Ys7Qe&8Ifq z`n2L))RnulxZc|P2}-UL72k66X9jDMz2u@bT_Z zf6soLohMr!m|A>O-KZ%#F@>Rrt@f}MS02Nv(2N%=b|0IVqTsUn>?bMDBlRCI#T(x0 z%`{y(Q}E!=d*+wJbC?25@*}z*KU!|lRVeZ!>pz2`d#RZCI`KuXUM`iiFv&Xit$xSL z+%ngYl8I~rZ;G9ma)kbg%$jk~N99_$>FdC`eA)5m)mML$vT2%Oza~WfPTJw^iA!4z zw76b>=d{u~TI;F(xq9-|_3^5Sdah6Tq8}D|yH5S6dAw?Ot;X~zW-ZTRYc=*ts+3y>c&b#el65}^pQQ;6zNpEi8?|puixo@&MY?hiYw)Jg@G?IJxx7YFUu5BBPZbtL(30>oFK27c8o0}5P zzq6-+B2-%p&PR)Lx;+^boIO=A( zR{c)knr_DPc#3i+Olj)U~I_OC(3^`ysjv zcK-bJs>xUS^R6fQ@#HxUq~ zUVpoBZE(=}&zf5P0b96#)f&h!?>_NHHSBw?hUJ9`s!lD5o979ymRJ8@`83hh#(Bc? zd7m#NmYd~uzghC*utx5SMOCTIUw`m=Y+t^6;uW^*+=Yr)7I-Wu5`EFc%A@Uczo3fc zi(mDJjon)RR^-k2+&ZPc|B3nXduw$*wg#9?UNQ6L#>`ieSDDSGi#Mz~J0t7zMN!u{ zQ`gVMo=gpL)32YLAlA`czUO=a|%S@}4 zU+hFvVr|Ur zNwL3{s;k>Qdw*BqULfb(L)Y>Q_)@nWa66KAtf%*&*Sy?^eNT@p@Ml;1oco;Ru~yn# zCvn!xz1kZMOLPQxwa!~{C`U{`Zb1k0m0gFs#AUxHE#1)b`kUVe#b>@m%WG8a{t z*rp#$f0=w{=gowu*GYdq+-|D%eq5uzeCs9u$VBhm{~dpdHb3hN=-Q{^n!t+{k9OxVEpLgUKMk5*qga( zM{3k7mOZiUU;UKjY1Rpm_j42XZo2d8^rsnmUXlw}>`aO;I(A9rdCdE?%$A3(I(1!p z+zNl4lbpdBcdWY5}@Ukm^enu(XNHks-u);$|EJ}=9MDXsukXNU+o!^%$ zTAx(>O-4F*M@2z%&4h`&K7X2*^Jm*Fr9a!3OfN(km^~o$Vo3>6{{W#$>_ajrh ztWIiHoK|am6R^0aQ`pURi<$D@MQb}x9GT6KohA~pY=)KSB%zEe3fq_4uaHhsTyCiM zmZM%|y5I50#4<7EgJQGqY`@u|cZ^Gl<+I_vEia$EdO6EEp}HnyYNy=h7r|L>kz671 z{(_%8Zoc~9aIW3rP3MOTwcCxpW`59-2oj9Q@69^gtu&b}Dsghr)2L0q{(OHs`6|oZ zSs@eLrgkmf-kdc@L82{h@=P(Ym2r+ypH3$C)k>Eh{H49PW$Ui!<=dBT{4v3D|EJ>I z1>ZCN3(W45nxUYy%iP*;zlzP1ue)A2D>S%!9^{YxeCw;Hw}XiH`KKE$-`S%4zq%pP zUEslC9mgJJtKBkQ1w{>$Mda61aZOz%y1;CghFtIV;OZ!;r#XDv^!Lv*kJXR;@?C$z z67#!!{L8(cMjTDhi`80t#IwaOv3>RGeSyxqX8B$v)OScK(X*Q~%fh`mv1TPpDm# zOq;2_)HkmBep8E;zg$*dkt#d$Kj6zj#W(Bb-2K(w{>=Pe;M6RUOV4|c{hD)%H8Sxz z>&o5BDomfw?DUL$E^fW>BYXawy#?2s-)#FFxTN5|#%Dn}@y@){Z-v$$%$WO2Ci&~+ zxSgTb@5(Et2FIQ3%jdh!&l#R`Ve!8ZA*po&EWQFZJsWIxeSWqrAjZ&e-S3z+>w*Z_CU8vK=c|>lR)6b9GvR`_PSl=3?ml-TNEptljViI2& zlhma?Rg;MF9N&AfpQj#Wo?Y_t!?bsz(X)=b23vlSUHQCv>%;Yaj3p9|ztUTRoK$zd z@vCIwUJ-e5OGG|{{0@f5Q$5yYyB=&$d-~FpMReM^=hJ?j^8eMhc1Kk)Ywt_tN>8Rt zPBHa8cGfkCqhq# z$0c?(u!+P32*2U&wK}nMqb-}#r{se?7U$C@{&FdHGLy;Gop`MIt(9W2OV_ePhjk~f zIH`KAtyN}r9UrY&X?+LgCxbI6ypyv5JXznitFdVN4lo4F`!_+|FZ z8}^^u(P)1;)ajmX$f<)_s#YDp*?W6F{hH?xSb1`LaC^j#`?EH}xJJCb&-iduYhx5`cot?B~HPX-C zwBb}bdw|!)yP)}2^1sGEx4%qbI8)#0Y#TI-XHu5+rKO?nf0W+-ZBaioW46cE2^TI* zSKRd`R!{BewtI`KV#|v4v%(ZIy(WF1CL^UMUbDO4(xNSIBxW4@DbJj0|0j0ctAt0L zc6-;GuB;O1iM2lTf_>}k*`D7xrd=`hl2?B2wBI(vEpnGBlWSUX=eeJ!JLI0dczq#+ z+43Fl`@^dvbGPq39dO}|G~2}^pBSb-Oq%#>QBry9?3ZmrJ}%lAEnEKkP?*pY87+`!eSG`7YlJ zw%PDY?sxpQ<@hI+g)g3dR7!h&WcKr~Yh{;N9FdVaUCFHPxOv6WFYM*h9X8IHz*qL{ z!utb^+)cG&-pp2pU)o`b&AecRX>v-1{*sVPQ!DD94aee%a=UZ+1%ISe+j z`APmQHSQB1s=RlK*J8iZYSq5Vl>6)ANozYcFPy}ASZv)=OAFOW5j#WLYNzc}x8Azk zD*eWk5W_l`<|5vMuY43xn?k3>nK!juY5I)W4=t! zrx`>NP^s%TQkbw$ZWL@!*v?T_59A9vHtSyO|#8>)(O=EbRoPrGe*{jYhX3Jd$p zt31A|vg?n=bD6K+{d$t@C;iAnhkuuNy<6gOb$6Hl)7(#=E=~X7wd0~~H2W*xuLlo3 z{PS5;f7uLe?@3%vs-;K5A8%Xa&S&HN@1CEnlh#Q!(IRf)ppMT4PHSemuKWCL?dhbs zM}mq?abY{-&LS1_NBVCku6Mn6(&qCc$Nt*=8A}$f%&VPVENQw{ z`Nt<`Zn?8Ns~mXe&7A&H?smmK?}ewu8nU+WNjae^BMNa zac})!6@^`W{#c;!?cSUo-5cD$ncEh8Gg*I?<%s3kh{!qKs)-k0iew+yxYpBkVPe;^ z6Q}DoXNIO+uyNElzIML#WFf{Lm0q#C*>RqmcBu8N*dp*gwa8Dk=lMc~;QL9~Tz;LiTXzt=26L^M54mAzMCV`+ihp5W%iLfO4pJ6c^| zKYaCm#S>qiS4U>bpU^)lxMuGRPiL zX51#F|LJ~kdD*h4pOSaQLvP1+vH!gan?nIw)QK{{%^`+?Rt4nmYlBS%L!}e zYORP}bW_<~@9mT${ZGj{`+rpE%GM*Jb&-gr#-(Z*IrjnJ)uXOhSdjDCp+HGOEP3w`)dDL(>tG!M|Q?H z|99))d$Yr9zrfy)hqaBrG{2khWl>d@cKF7XFY4FcT{q?3Or3eVwRed;+V}LJhQim< z+DqNOuRJYNnxd}0e#0F3N;=m;d3M`{RA?gTMb7@v-*x##%I?fb`mS%$AU zVk#3Au0JY^$S-j#-y{-OlKsp@uWaR#()Wv1x9pGM)1D?WYx08A`#4qytTfH`3-aYV zs<44=1J4(3p*w~_(&ZafW*2;^dHQ#%&e@Bv`p($j7kc&ScB+j>)oqDQ!b?Bh6HwjW zGtKN%l(eKsC5!cCx4va}K5RKQGyRdZxb}*3(A$0d(?6G%FJ2ceZ(d<} zgK=w6lwy!`VVlFLE!7x_luv^7;aw}m*sS(asHHd$^YyBt^L3o{4(XA(FUcR z?3z<{R`H&@bxD5}f6*Sx8LPI&O6h4F;+l5it-H;oJ&qqkbY8vQ?4J1AfHm~ta^4)?Luanqx)?cju&R7lV&Eq%>1h%u=+^IedatXQ~TB07qzXIX-~F%!y2XZ zb*sy=?6(cF3(tsNzJAjAtRU0wi{C`~uV#jb9@!8rQ4nW*M^rhW=DLi7_0^WeOIX+3 z_Eg%v@1#j>aeZ;7og9Ol@SZ=$&$qk^$gpZ&`u72!y7kuWe;%bT*fw$1{DV(Fu&`Wk z=4jI`j7)zfH=oya#U$3?U5b4-ezjdW-8EyfnWKZb?HebG zZda{qTk%T!JSU^V-FIiIq%Rx}%&Ay%IceI?l6OU4XMaoN9zh~kmnZnB^JWu7* z1)Jq#T9(Mw?5=ld=i8z$vMi4$C+)}O9!A^p4Yr}uAN(t3DE@fGv*46c{L@`(Iw73=TqZu z;cKz~jHYdTAHF8(e=K+5%O;-_Q%nxt`uA{-XdDZN{qpvkC;A`rt>3H3yZGBd-kzgZ zbJdl5?#{DW|MM$nL((%vs|;b2th-w_8m%w6uj-OplrzWe;HL#*eM`>vW*x{`FEaJl z4)06B+q&I!=4O` zEiL;3SQ6)NewL;GX~s*%rJtVPHL{Pr{(ym*le1KVYjsNG4d;0xnZW@J3~WLIlL~(9 zJ~B1bYSwQ2^;h(BK0d&}z`&!T zDr3RaCM5LX;spjC6_p=B6RgydHrQ~b%Ngih*+1ym$C_K)d0sdPd%U}O{(t@dXx+%Vr%rogoOBQLd=b@&jk_80 z_sxEb9V#40FAK1>yyD~t%1qchsitqs|N3vd^LWx&^e(P-Rn)A$z3Sw{KIxyFsmpXX zJj}nX|G)OHD#sS<>xSF!H1I5oxBGbNQL^vf#q}A7=j30qELg@nhu1(uuk(L)mCe$l z#%XWzS7+C>|1O`%YSS%${(5crZx(skQ^)ib7$iKLdR*K09iFB$QCR!+H1EqNT{3Hq zGQG{6)w%GP5u4eKX%!Jlx5~5w-(0#DlC$c0NwV{uJ5vKb{OGOr@LGAW&DB_G|b$ z)^ztyLg_FrwHZY?@(R;P5<`gJyJMu*#&_51^F6y`@P_70So z>LxDv>4T6>vg^~|NPulh0B_r_Vw5uY2wI3$OB$Tc5>0_1A{uhwXn4o8q z{CMlcFb|Eaio*RXIaqEV|ME*!(Ov8Ga_Q)|8>C{IU(dYz`^~3G%(eoHX0J3VyRpCd z(-*0?aZmP|2h|uf5X111_F_8>1?@THSW4NK(zN zB<5F&f)74sTs70J%Mf_ z5W()P)?_k;Z*6&VYyP`4oi(qlkG7xr&osMwnR|6S&*bRsT)C5$CA8gY+*NpKqolld zS$onsc8*II%q;?EOpa2uZ~7f?bwrB))YO&JAG?daYAW;Du+LB=Of#rb&*}kNkIkpH z9;vIF+OC%0<4n9EfAu_fZPP`)9lIRGro!K(-}#uIBrm+E zXNCF;=7(=RJ*O?5Qn^d3UHwSaeD8Y3Ktt<;TXgQmOYCCJTY6AXu~u0&iOFyN%hhj+ zR~>#jW0lsYQ#MoE<}R5PeyF=%C-m@*p2y*9|D1k(`&C~?>Xz`7g}KW$?!Bv32=CLm zKGAK}f^Dl4ON7%Wc$rQSH8STLmefjb z*|L`Bh*E&wt)0Tg?^hhqTza}QW`oQ7xo1W0OqzN0=O^>PrQ+_7s=Oq{Pky|)`orS~ zrH^-fi9W#9=gXM??u-8r=$GThci>Exf8vMF-%Hu<2$Gy`(1$7lo(cZdO zP;ueHH(u_tc0L_duDPH5a^)Y^JdphRqwb#c+WYhGMA*Ewel@Ro_6e^p=NIdzr-~Op zj8T>=Ef?Fn!cyPfvN@7@rp*z?d2_|ra6C_}yz%7h?`6jit_T$h`4^FL_WRD+{X2a9 zRgab3FI`E?CDG)t}52PqyQ`{Or`iIrA?!D@^7&^Qz+V_Sc@Ph2kFQ_q(o8 z)QWCcabi|`@sXmQ7~LPmOYd(=%_%E=z#p_(G5Ztm_RbGw(?23s+_-7- zlfunSGJk)`B~-Fc$Y;~<^t~CCc6jFgNs=74)<0G$dYoHUdF1%t9W(j`l_nK-Zr@%1 z>`v2P2k+RZ{NsBLWtzmT+or3)IJ5h?-^-;>T%Wt{{t|qsug~+q*8kT0J1dsn-OsV` zaHibldAqLsE@(bGd8cmh;eXbRo$XCFGS-Xk`Ml{mwXJ5_k#}pw-FEoSpBv>o=Z>{@ z#MYLp1qW~G9M%x*DeMm`a@);v-uf16!b{`5xi`W#+%A%1e)!@#d+n^ORZ`p=gMK*n z*t93wI=uK18!>-(EdSg|OMjWkM`InXn|HO%vL zXY9o9NwdA)KXjVAEGkC0py()zZ>h;zNnID8;7^y<6+XMQGL6gHtdqyOcHOpw1Ltkm zY=3E#fB*NLO7?FLE*Sg#n6OCyOrI~-ff@b z_oMThuIk+r*Li$ujVJj&6uuFpRa?v~aekVlW$J_LlYfYR{d<4$BJ+x`oq~0)*5BD9 zZx#Fv-Bmbi;ev&t>~&tdD)bW)I3^s;`yp*T{Z)nLvXy53Hkms0_mbuc+pk6vk+}QrXA*w5G`KM%d5#Ilt9P{2aPyZab^{?$5*Y-ub zT{YbIP5SwJ-mkN+}-Tl6ZU-Ge4^yP;bGn{ z8~2$mU$Lz}r1rO$O`zV?iqqAq+xdhJE?!c!<*lIH#$5t?&q}(lpHp-2yy*E?SI;yG zluUR3Aj5XwciUT`#I}clYNvg$P#0|HV;S3y%a9_Q%NRTxnj`o}|^fF!FTC zH(kSdPv-~MeC}ChR`mYvyT_A$y?K1jnbkt~%frTHu5T)|UMZYrb^RXHs8|xy5Gy=g z@N~?iuc0A+^@}%NWfXJ?>AtmKpPG2Xk1N-=&y4E-E8@D~lT%=g&@ELKHT*LIj`nP^6W6URFPv7byZ!VM>$zDv3l7CTTBvSp)^O{@ zcHj2Yty_)vW;AS*J*W5J-8EY=pAF}Q{8?t^%|8+MaE`y}IfMCmD{bEVVM_dZRqj~j zY^E1;HmLr{is1jmpV5dFa&)G!%pQVYxJ?9Vo*E1(vZob~pCymn zS5uJreP7%EqGi`_!5Y`umPY!wMI(xtH)S21bMdOeq+Q0z+&)`{r(4X~Zprp)Z`Xv@ zKWwqvH(qL!ywG?@e*2tMg@=E$A}gm=xJK<>Kary_$k#>ev;G>s2G>1Z^7+D-H_zIB z-ytBjNh)FvJFh+4Y?Ef4S*d&NZY}6v?8~rj-xW5-JLbimvulFCuaMb!%Ue%i5@Vr= zn)`=Cfhs-9-!GYeINkC2UHp%N!z~#%t{l{A*p^uqbFj&cH!kUwP>JQcUux|O%)=9H z{C@Rj%`MtEl`Fsd?vdu14|gnxjXxH)M#Vy9(`&z(5}(ozF)KMPsY<_mT{l+tQlr3w z*tHQw#_^y07}@z(sh{riu*(k3J_~{|{aT{0vjcFQ(wNG2`iM_9}YG$)@ z;~Mt~{kF3+^lsPeXI-BYBq&lc~zowha z(`ET4?OQnKMZud5evhUKO|3Y?uwna3o}`ANkD>NLrr#8MFR}+Ri;A|EpE7P;p*Q!W z(NF*5KkN?7I$_B!-t}oN(``@bTW38!f1g{VB9wV+y<|<-6XAfPeV131=X47(T#xGz zKAyTv&Li68$()tO`OfoM?wn8lw?uhkLG2Q8tIm@t(>1WF(-)E@1vNwe@USun?&$FYoLJ_;a|Lf>C z%6#(BP`bzR#ec;N_APe$+rteM_FszJHzW7)vVZ$UU3}F!Sv{t9S8dCj=rr@9fz_S$USH-(uDEX6m&^a3 zZT-Kg0w2>Jq-uOQ=B{%g|Bb`U9bpH0dRHr_I;^^_SA2BAp_xZo)wnJQ{yjTOPAw?= zyzn{JXNQh0d+Dy5Ui(lXXWrVITRv|5Fj@5d73UCMsF`p$Y%6^`ltEP(~MI4*eQqpm2T}_I;nAF~8-?_pZ;c-toN01uxXryE3VqhzWbLRBA>4(|vRNuVt*fFsb?IljKG< zhaasO`MDxqOpCSJZ_YS7LFQ_3SG&PCoo}U^rgAo4Iq@u~+jG0Li%rhG?9cEaBODbY)~nB>w#Lx$XV;O7-)mXys*`7d}{M%`cv!*zaNb)Tny1 zZSu#Kd1%SzEo`$oJ+sa9Vf5N^Nrupi^SF3VJ5;%?68v0xjEgkPH=XFiukq16aAmR{1x!rqBtVsYO9Fu+ewRVS2X-m2w-3B z66|Zeui<6FVctLiC8uv|zevAG(zWe((*Gy9Kt937{b`|0?z7*f?Qer7too(8_HD$D z7Q4!ivc8K1HT3>DM8EP25#L^Vddr>U_g(KpQa=@PRN4RF@nF~YzOnMpomKfRvWuCw zZQbO#eu46g&um_tpz+iNZx)9{9C&jeD|JGTis}b<@5ryZI&Dn}yf4;H_nEP(+yCEp z1LN0c6pEV+qb{VXCo8VLTlDX=I%jzum*4MzkUzVPPue3{pfsHi{h zTD(ctYL(Yc5*z;fwqCthO5@_zg&t-TqON>;;dEue3C1OIN%lV_{+u$|Y<%8zTi$20 z8qY;f_Rr1y^!VwFf!y#rKi+m?&hyv|j7rfs`5`)i8#?Rr>0XY&Tg}9XoX>d!G!7iO@j|9-)uY2B$Ud1UJ8rfr z>o0G9W>Uj?oFSiI()^L{g8DZJuVf3otgzHhW8Ye7Q%Z9#pzPAwbl`D+VeuGO`qH~j1? z{^2>jxu?&Nrf+tWoSP5NSX@U2_Hbdzr(6SV&N za5glY_lWGe>kzCv$?}%RtYhMO8nrB5*RP*<7Z3f|YxC94ziqbpt=(s*g++U9adDll zwf(Yj>a`CG`(3YG-LUb=`b$!lyERNgWqX^nq@3QjPujH5C?wJFq41%J<&loor-ioN z-?y@@FV*yhPSY9&8MXb(y?r9G;GA=Al_>#|XKXY!pIQyypoblIX*Uet3Cj8*_`R@H^ zxp#2>zIw1xl;^$E=Xp}S3CvG?Hyb8q8S_eKYq;9)F592-GH(5ZNmG@TW^cQ=iG6#= zgo|tJI|9~NZCm;Mrv!JJugcw&L$4c?-}&j;X77qNxWl`8v-rfup9w7qQBRX+Z=Sk4 z=|{?;9ZfvR-!i5szF49@xl>0V(RBJ#>q$RdrC$jdTh@fM?7sD0bCU4uD}|ijt7{07eyUE-F|3k+~(^HD-14v zP?ye~ygM;Qu^9!&$ zJ3c(VdVS^I-{)^sP2RuR{^Gmq2Lkr}QJU4aYMK(e;;OrB7aiyNmZY~|%Rll>D$393 z&#e1A-@QN0)D08)v@Kf8P(u|B?OA_V9C` zQ?S<82<^jH4+rbuW#9}T)i~6={>Ww&VyF}?|W;TW*JpC%&C34 zhR1t(W6q)F@nN3zt~EyvDtO%c=IxR+$*kP?S#s%%BYVAjOe`*kb!6*691q ztNFq+7UsoWnxJxkDU;2xwv z1a|Y?4Rf9QKtiKf;+;X~@`Ce)0pGW;?e>0hu4(ocIoDM$y}JKR&0t~Q8~cq{Gf?L0 z@;8$^7Iu9#vD|vYKcd{pqj^=S;Le<$iUoqR_G$lNIB%0($YOVE{`b&09>;mBXFojT zWN@Zw2O~n_nN9RTFD;{D)_MfSy|<}xWhPI+4jTc6Gv=K zr(S8f%PJ>j%URmGOT>%`}^9rpi4{Hve`0&nzeFX`0!6Nt~osI;EA5ro$i13 zbyXC-wb`}F-YsO>Jo|^AeqOel_`@`FzNLp(#`U+iy-v$4_ZQB1oT*%}aD`S#i=oVg zSO2mfZq~6?m}sLUe_*{ypz+_9{TFRlyel{oW~|w0v*y+Gke{8AKQkvA8cB!fdrQ^% z?09}a+3}jce8;iI2e(e%Ref9`{%A%2aTUS)Mz?}9OFmzFDWqz+O7P$(&so9(wG+AS z{aBp5bK(6z|5+xUWS^pb#NoK`)WG{mIWKQiN6ssEOnCRUFeNwiU;g&)`#+19*&Um+ z^dW%KGECZ}hXc?^yG4VefB~|7xcLZg07HuZJPm zUz6$Bf3KfwEp?JjcYJuNRJ~W@NUZ&w$uG2eIxd~{F+TzIcdx{_69ep4?GaG1b**+x6qo-tTvkYo%Bf zZr+wZm_3=-uJ^R^>?kl+JF7*9j>Fsipf)tPCqH@IdyAj`Ld*h>$4NiE&zu2?2Bx-g1d!gBOho)-0^LW6{vdZ5uqO?UNsgKdMck%Ah?N*cfXg4YxbEWFA46^UK({ZbqhmEXq6v(N#?h-n;ruBY!~NxuHVX0u%u*p z#26A6HXe-u=h*I`Qmr#VH@2PAP6r zvr1mWw7Egx!%yd)RlGZOt}dxn`I`_sYhUuowjr&Xp1jBKmSbO-lZqfj@k)I zO#E=ua-pfmtdGJf#3(j}UwBx_%XWu@Yu`W;X_q(Xro3}1@nJbhy z+PJZtax&FC5VP<^rh$By?ev~lXN%?w3D4dvY2)2$zs|(KW>v~%#k#3p=L71}8uMc3 zZ&)?G69Rz01-!p4V67ds`LX zx4eGsC7-tkJ#%+|yPR(9a5n05dOoYmg>LtrwXZB4KQGJiw&OY3eEOw98>q%^Kc5_bxm)&A-qA2WA!&Cn)8p?~K6dw+ zZhh{x|E#+_QL$ozXCM1}Yc7+U==2H7SNBU^2~&#p zSewJDw6pi_o&(+4;dK|)T%s(LChb~en9Ms#e%(Z8?XFV`zNW7aDc$GS!&|@KMa_q? z^>WU?ukrgg)u!m2{KBxWE^x%kXLP1$#b5k*|QhA?st&7F!8`Pj=%D6 zlq@P6&dhRdcwQgXp6u}D`8&qf>t87>KDA}Tgr|2tH|3smIUFV{{8u7s)A#R5efM>| zFHZd{zbHfMh3l3ndZ&&bT*CHG=6u4W78NPG50%xs9qd}`rX7q4o_l`7$K0&T^JA~< zx^g3l|7mNCO6h^0^Bb91B42X;u|4}a+|8v)CaK)7ar9I9s>t{+VB!6Q7H!&6j?eu+DW zp=#C+UyZpfI-Igg=N!H#=H?eJ(Z2PLpw{OruQ*(1HO~8CX&l!d&op_t>}(h2UW5Db z0kgNUNcY&-MT$P0sB-hjq8Q&!`O{|(^D?uQUwxV?CvD1}ay(@JbPb`9&BuN1zkE1! zI$z?jmO$aGi`K=vbMuevDi%2u|Mtq>V}F*)Z&NSuc@REB;}_@GAA+wJ*f*6HaNWA? z&UQ^CD)D;dS=qO%$`3A_?BKy>y=39!%VtY-WWKnbp0w%9EtW^?)-E{xg@+}gQN(oC z^vykTD~eke1#4|~k``U@S8Gn&9rndqzKhHrd`Ys(3*FE+L0fTet>d9>FAfC%JA2mr z1=Ih8jQ(K%UJf;9mOC?~EwblsVm)!U;C*XfNgMm0dD&k~|H!RX`p_!oP*k_ZeA(K& z>sDN4{#-JP`*O?cc?<3>sBbuTsPU4;lGpVs9M+fYu;_pGrLSPNvZ%~$-8c5HZrzRG zGu?iUODER<2RHxY*hKqPBFkidIZtD`Sh(&p)00`w3#@z%RyyD94A?3rsK_YvH*)96 zq@@dvGVd0=|K@=j(?3bEnariL53lzA7`5Hu{(ik%m-j5V+|^yylm7R&-6}odh1v(s zOxV13e@N$Y+vHOMqW?^zq|_=6+CL{80qu6J&qd(rY~t?6`2)3%tkRes&Q)839hCBMZLUq7e6WB%6FrJ5q8o_9ps1zHc^ z>eSs(cu({E*G)U?E6yga)LE-*w))|_-)k@2W#vv$H+;k+d&xxRqBuk1asDXgvnF95 zyfeS=e_kVcZS~g2@tp~td*Y4?Z=9m~=Q%UOwV)@ZiDuuIriHZZuiTX|;h&lIM1fx^ zulGi&cx%qRa&*#^Y~y=}Q}6xt-^d@#rIB!wQDRp05kX#)DNMFIH$B^%&Rex=)t+Z< z#@822ITLhVv^I*1TisQDmFBFeYx~RYwb=jgoSOJ=%e}?F6Jpj}$o=@?-{K7A#}A9Y zX6%3UR{v|+)L*-A{Ef~09~xS>_V|D8&HJyek6-cnYe@WRKl>ng-p{M{70-Va_ctp1 z*RJ<}W4GV8-@E(ySI&P+S3iH_|7*APe09(Lf%{)=u@Bo>xA+|I=LzjD&rj_8`EB~E zSM6U*pa0q&@i$iLf5`fpzlHy`*S&szfBQNAuR-6>U($ZAu$k9-{{Pz4$?P4U%4U81 z-OT`6LCSaWfYT|1tgQkDv)=1`H0- zH%vPY)HiL`c{S_W!fd9VoajjN>eEaQ^fz+2WHkrHFW@?&8>r%VNA;s;_*IR`0tezu zN`D?(`Ho?C| zY3*}coqM0o*b+RMZQfn^&*yh^<*Up{JU-85zOPm4qZ7*l{|WEA^e}oKk37er%F_S# zPhI|}KJQ6U{v&a>?7gt6Xl9UzV~@&}84MTSS`=*!wtePatz;}7T+=PQ@9inJO`Wfg zdhV;re5!YzMMG@Gif%WVgz1kZ!XG%SzR%WKG&ef)P@18T&&O^aO`B&@uB_}abA>E! z$}YPznO|i#*KZ?_U+;P+*|&H4{{6Hw=)c4zHj}K)#XmV-@hws_EW9CE*Rb!C28Tmn zRDoLm=J5MwRX*!^qSBTHsl@;5-1o^~drsZc7g;sF0h^XMPq*2Uck_tzA=j7tzkHNx zxW43;#D$8SD<^pFW>slb1WA;4Je_&u&B7o5W@xYcG}r3gm)?J>vi}xbYuW$%w94)q zom)(vv@ZD?r$61~&X02UhJ)J_H%2sO9P{!^R+#l_n+?zLP?vksKUTWzw<@XIVdUdF zO)HbV^M`NFwlD^v32qLXO}if~*?f>Um49XDqy4d=yFNH=xyzszpV59a-|qHGv69&< zHl_=#wl#M4?g=W~vFd|(A&6sX3=2>5AeS?`PIF}HX+SS?Y5=ZqG@a$p&LH* zm1s1m)McL6@Y(V8?^o>$XT`o6#Z|L}ZE{!gTXdG?-eiByCGVPdPhGp?>)csu3gZQC z_A#&dBCg@Ffpxx6V*fM!wB+S?Tz?h5a_U%+@V~6F-X&4R{>qi?yIrQxJ)@m3p(*!|3^WoLb-g`<4k#|3c@U)vlp*}1G}jnbLu&ZSK}J##oc1ODZZLbi*yk_5tzSFX*ShVGxg6wtx9Q(e&X&$s=eAlK-dM%My!Ef- zr>TNTBZ@%W3<*<{UW} z+nUg?zEhjEZ_eRH{U@G5|NR$kd|#DQ@a)P}`Jh+o^1pIF=a+A^K2herub4aHV9*?m zpRdAA@2Xa8e$F)E-1=KDmh`7wIWs|8v7_bRM7Q;gWyk(0PdZVsdG(Df<0ls`mKtAw z?q|0(I(UAB%9_Xz3ioWc=B`?25NQ4K_o020YG+UQSaErClA-O%iP81_j0c|>w75W(4=MY zYu@!uZ2YAmVy(5}Qdxsq(p&|VO=)furK3`oaUO_C^|M$P}*nPk3 zKY`kpldcJbE^RF>C|wbCc$?$NrG|~N?)Qttr+(i5r}xd3Co(p&&Ba?goxAvq&hv=$ zG-RYkJ>@&m;P6;fJF6t)#nc6M8ETh%({)029{BOzeV)i$*>0KilFuL9rh0o$t^Bv; z>PJ7hTuzP4{R<0NObVP1e>?J2-R@7uQjxm*`)d{Vu&uaKdgr9!jj6n55MeX(fICT|5-Ca0|mG20f@$^7zAV~Kd=@lxBDM}2#- z?CcvENA_H_aMNSwe*I9RULrxSD}!fMGlTA{nipGV7Hy0)V|vUvXUXB2yqyc@{hXe@ zY>m*~bFN{ZTDQHbf1&au;^2Po{*Lc`KlZohy{rh%$uQbFK|<4OZ%fgJq-mP*9%gAr zo(FO@@-Mr*KI`W95Q#-LTJ=m-f=9l@d@}lajAe*)l|s~To?Q0*!8aXt-E51XRVZXuARv+Wl};- z`^7)=j?NB^Timjd?fi-j&tJBzNtpR+r?Zc9_M+zp-iK7j+Oa0y4c`7?)zk+osNOh6f#7PFI)5duy~n| zh;S&E)C!?Lt!rCe)gN?Y&e2a_?_0L%cXriVR#V%1|K%U7?6dPa9P`KU`^Kars$nlV zjqXgel?wf(c6RliUahX1e_y2bP1jm*|HMt+_%j=p2|4m_U!=QxpIhOomAqfA56%?W z?7ATOS>CU%Ce}vlmyNf~PMpx6wPe|0rxUWW7npzlX6(pMG4d5#m%BdORmQKu=SZF` zLv-c!wV5BjO3nXsZDv#C?)ljv%Cp@}T(>(*bYJm{VC8s|lX&Iq@11`$ujx&0{aao= zW&7<419y%u`GR@p3!6l{41A!1L`?=|BT3vwW{hMJ^)K&hcKmI57^-a=8hNta!!+jcBSAC8u5ZnFl zee2{yte#ea3j8MNoS(C-3hfJKyH4xnDXz~kxcvI)JeInJ9#6cF-|JmCU$^5%M8sm7 z(~Df&?e;5V{t^*i=W6bFaNq8y*7b8_YI3YrZk-dpA?&Aob;Eg;w%4YyPtNHqK6vcx zMo&JqZ6_ak9-J8c{Og@P?eA4*{N1X>%f77F{`{0R*C%uwl;WH8>Px)$m&(3!CDFIi z;nm#@#osoBJosA?b?D_<;q}*9c|7HLemt(+-jX(Ffe>41lBl=Qq(>1Q&Rp;P1?NeK zzI2_q)t8lLt)aW$wna0!(ocVptUR~2%{esO{gBz}?=A-aS`FK~ayLHb4Qt%N^7W5( z``-CdOMG;>*SWX*RMpq1&OfeOA$#Oiht#o&?A8p0#h<#Kg`C^Ev-5bLm99H;$a6#6 z1#(|GF5f&iO)2l#S(AmVcFpz|ckd6W?Q}Y~PpV{t)>$9B4`n73-`=jNS|X|yA2&-N zqFt-^<8k&I8n5E*{Atu8Fnlx=@5J?y4Pac{F58m@`=fXbXcz@9eCzVSO*_&)r@tDiEo3 z@a(MHA&cG_lvtcz+q+@%alRWf_nl90{t2 z0$cbOJ$4?0MRlox_M3NCoH?1jbkddF%bK>LoTmRTJ^p2g$H4gv&>s)?UHJdbNi=JzHyyIj4=0yv( zv!A>Y$Z}rw>#;|Z1GdL4X*54j_)|D4Tin>@#0sS+=hu9Y^Ej})(yd6aMQpP^%X;QH zDk9TDWmtEbo!&QTL&bsm+@-=oqN%?eHtY<%_i5&oi%XaNp0{$6)?LMe;XK9rC#=>` zYH8f7-Fei`>sO7pD2MoDxoEksT=EZjd}U^eZ93WgrOi4htUu=C`Cq(7fy-_^Iwqp# zJ1P0)tdv+A9|j-0zn80A6k7kVoMx{{TQ#9JxZB}va`VJ`&YL?ri}ZUOMQ7Ml9(}Xs zgWleN?{}y3F4^cR{rd}p+B~(@Qj05Q3vVj8wcFG8=LWa#nSUlV>r9yy**g8cv3Q)2 z!KBr1tXJ#3sb1;0;)O$X&c|-U`(nvf9x+xcbkq7<gMt2<98JPP}r1SpHXuG=V zPxE#@{4?v|WsUVIulb@Z*G)|;Fq=Pbc8}ys>7)5gD}O$o9}xKIya?xiR<9X_`a#y` z4dwi-vIRUH*Yka3H8hxhT}k86q4(3Ko4=XFror6)K=GAjw*T6PpD!-;zq>Sd)$R+S zdWw|_&aU#ipU$7cRLY+Fm?5dId*-7V?9ZyNhjE{fJGXjSjACI#rpEIVjGvWve7bHJ zUFdq)`25K?J8gZA{#txWB|hA^;S4uF!{yT|>#pB9sqK*z-m02=#{F-_e=qr1UDnjT)LZ+lXItl! z0+t4c4QmWkRts4EmU)@*h&S-XZQFDF_y5;(ZkouJsKM>0)?HT1#+bjP^)BCAwZFGT z=kqQ5pSv|ucVC9*R^yE1OX0Vb|NE~yJWXY{)#?imcFL)6PPDeL{g}x#{hZm`!rKD7 ziz34oPkp{Vw1>BETI>9Fb-BAzJ{{XiRxCEoRTaH?X3DMBr=6?j27Eb}6jpX)_ImBL zeTn}9U2`vI{g2(umbH4F9!HT9FMn^q;`@L2Yg^y=MW*eJnXSKl^?~<^X%Cs7ADUUP zyMrlmAz!M|kNF=oJgpjTi~I>`kh$&+2D-{NNnXqN{hxj;dLn>5ozU_ejd{ z=tR-kGhVIP@%(ew_MN7B_O;EEPnRG6y=T7uwls~H3Eu=%(wBbini)D*<$8gT)}Iww z>=%P$`Z-U`^t`s-#)*-8_U5&BJ?H-V_j|^!{>wo^Vv-~q+EPV?^vitR{`-6O(Vv@Uou6dwabo_gr%u`DD_uTx z=&W5`6@0kotGUizXR{|->}A_ZGbWr_Ih%jk#IC<)k(Unad;cjeMR?x1I)%6snww<= zjy{Uf(4AGI=y+hN+2olS=MG231-B}t@GP=a-Ls49))gg&)Bh&)et+p;mgm}VY?Ip&gQ!)<#wS)j<$hBfPaT}`z?=6e+lCuHtIAvy%5PNv z`OtH9Ia{$uwBDPDZ5v9Kay^#c8>VEMv-4DE_i-Nsrx~_Kug^Gt*l147C5i4OIy6`@jD8->WNc zI`*AN)%xyxlTXMdPV%lX_swRprIY%!h z-o3A47qg@2Qp#+ZZH|wRYb|w=_1YfDRx0mVy)@!`Ty0mb7hl!RlBSA-@lzz-oR}Z7 zR|w2%?GgENh>>-JYHR(^?U5D@pBN;vH(ogI79>$2QQw;y5Y<*zxJc2?TJC?g-)o+JgwuQ3_w)~MbRqHLe zmiRWqI@LMCUH#7FgNIM2Zu^mW!HvU0DgVWRN7+^UUaO}v=yu+as*{{~sh+{yn^)BkK5qb1%dGl$POV&}-0^;5DcI=qJe)5&k9Ok|R4v7j^wX>SeW^S^a zJ09JrW$p1QR|SV3LX7Qpj$e}$%C0&k_lIb= zcLvU_Jfa-A%w^BB)eAENLvfOIzDc<;Upsrsf4=srFTCEN{IFpRXL0Lh5AJ)}CR1d@R-KP}zx_w(-oy8% zuGBxq-l%r?(A0AcZQm4~{@%aN?*2k`xrytHBj>9HA9$H9Slkq4n>s~g%D-LbW}An5 zI<;;y4zRZ`{`Wol{i_9qx-*+1HoUMoB>Czj@7G5Qx8GnnV!Qd!k$tc05_)G|o@XQ9 zlEP-1Z*pGyQ`9d7>F3t}5>$-WP7M;?z9&BH>gvBC6>Z!3jS3gsTYLOyR zWl{dRtar_aLZMYYvP%}sW81sx-lH>Ff4z%dG=6zr;9q`L`ilX#v0QZQKU-mbL$&_R z+y6XDUt0QKB=+r+g$z6@ipOU(h*dC zr)MSKf+JTR`EpcX&c;VaJ4%0kx0uqlAizlHonq?-$>`2u&Lgu_t=I2Ktp1X!+rRquH}U!X@e%jx zccff&a(Qri%`M;X?uXm7E-ObrPCq7p`%28M%87HP-CJMZlo>A4d@^NOfhU(yF_)!Y z@06bT**yE}uI&9B<&l=I%)}O!ve0Rwx_R(M$)at~Cu$vd)Oq_@)CH;Hsa5h`HtgCj zvN$4Y?E2oWlVb9`bGfCvuwdPj?C$Vd)nNHaJnRRa^M5O|bki!|v$at8A^*bLhB_WG z-+%Ln{9Yt-^10=mZ$`QkxLovD)E;w5e*bS4{ZYa3WvMjZykY~UKlw3|om=?dFZKK& zyHPN3Nx_$ny&j680Z)r}{MHSbAo1EqcEX~0f*&)vd&RQ**C=XJ38!v6Vxk>#P)A3m(i6^wIJnzcu#Z*G7dM*}k_XRYhHjF;>G z*IruZc<$^xwLC|;T|2oThf#dp6#XN@sW`#<0K=CJ+5Cyo3CuZVHuC>v4}RcY5Po1f z+v!9_rFzdNd#^D+R(mVq;rZPqDSDyS&emyd1zq+3>vX<5#@A%bIc@an(w?nP(?#!W zo3UX3(Uyy|&&eE8i1phhds?91puX~9tg@AeZOykDjyd<-JeS z>WY(xDVy8WS23ndsT*}oMIUdRllY8R?Ea!Z(-)@kT3)*ml;n_h`+L~ZrI+^0Ih(zy zIC!_{Ki(}_roNL0> zxAPv8!%Y?~|K|@fn*;CJPjggUm{fA*r?Hf5in>#4fohn4nsCpl6$@S8erJ{~y(2BP zNcM87-=joz#p_<@CrzF8Z%(I7{3g|id9PnKmVXnK{kY`MnNF9_6SCg3-L%_u#r?9n z=_0$ksoHFlbLw@@_aBzs78K~Q>o?;Sy+svCGXf`HIKc8=x#ZRi&KIGsj_uzLoa>)o zy~Z>6IZxs(D+Qw`i9Qq8vpv2V*0lRfc=41@=8qqP&)?j0>BLIy2d9_#9mzZKQbYDu z{MO@-CrdABJ!a(nb&dRWaT6iFBU{&OKb3JY>A)nvRTJ4P_b-f^U(Oqp+u=4_(5cnl z$WLirpRJ1t!;+sEEG{|K{)`22fqYH6=u zzdLKMQkJ%~e8Ge6qSb{F{THWPnC2wK|3RpG5%U9C4%voz&G)Y8%70q-?~f0Y)FMQlN(MxMa3@4ku`t?IhmZ$fz zth8HPao}p)`{FJ0Pp+CEdT4FQn&s!;9(X4HxokC~kd63ib<6%0_9su+)_N%3Zgp@jsjhboEhe-|K8w7zbKAAbZ+9U?Np#)m5tjc@e@DHly;uwy%>}u;K^8U!av#MtlY+X`#Ma@)(J$M`14(Vu6T|1 z+}pFtF2^ri`{9=Dgf0KL+0<9vu9IYFoVURJ5>E>6A*0EffuZbo3L|Bkm~X86<$Gsu zUFd9!!>&q~XV}*ApL3n@!>DD}j{V;a^b1|CiEw}4H$#*6&@#=MXX2B@Yf8BO1U=Pc zNqBT(l~w(MSK0d?s!mNDsOao=7cHAGtU00 z$PiW;R&zzxP15az**0!b$#1iN1_XXR&$q+4>GXl^&DYI>kADBJIWaA7-qR`F5x1Bk-*(bV-uko|I)!Df7 z?;+7EshVk-eJTwB4z^2f#j(F|jdK@gUbgqwja$KYU5hqpJEoZ&n!~c|T*${d{|;{@ zx#Ali?yWg8x9HrX&ZVy^_-`GtYn*xZsc6k|Yuz8O)|{1)*SYg3iuFiNgJ_{X-?H}$ zKQ52ZJofO_NrSVCeksY_V*CAa#i@lWA5A;w&C#ZM|7dK?q<}NC-=DhoaQ_A!X~CCM zT#kP^uc6em$G|qV%8!HfU4VM3Psf7fJ-ngsn|K9Wr84KI=zcc8EtmdOtL7QFKwo4uR5aBat-Htp-D90H43wLYdhe&_nRA$yl*TGMr@7>hl>`Q_SoFOAoH z;B(gbZfSd2TKBPyTf3hA)LDJoCepdgW?Sa3l9SF1zEVqyUpIt@?CIUI;aHT+S*sNr z8$A>Pt8UBbb+dBhPRQ_U;kWDi9_F-Zzj^(y9Sps{o=i;py+W^lr_m*cc?t4smm4hj zG2Ku%%*8Z(^Y<&8I!jH^1o;#$V9@ORQBH>Jyy zi|=eV+{!k6+q{DnX?r~Rn$mSnx$oWg#D0nV+XXvi=OkY#z26wUAfEB^DN_d5`*VM1 zO!_)S+i}lism6oX54&{XxEP}TG$JCvB)Hs14K5s%fgin;pi7?*&rt zsvYs+oalAy^`YgaIq~j!fsVV|ICCs-oxb_Y=lYAAj{@Iqoqz7 zEpazp)=Yip_U^EPElJfE11(|=8cx}l{B`+Kd~S15wusa54gF3*s{N1b4@cjaq^_WR zQ6gvk@;b25fR58#s=yye19)@ z=DXI@whuHNEp4px7)-tIeZFP!wEqcT-RsWn$xCEJnty!O`Y(7iu4J;UzQLw77q@(# z^@GLt`Pq*q%Pw&z+NrMa_Le$6v2yO)WpZcFukaLkbX(<|XTGY^I-S2K-XF5LQ*}OQ zs*!I?nVtN*ZNk&O%+LE*b}}J(xKm*|Tha>Hm;h2GKpY8pGv} z>df1y`OI!wwf>HRH;kgwZ?Cv#^WxjFhF{c4fZ-pb7W z7}z)A`bWllmwF|i^#`VV3gms@WSOz1#wvfdrOsp4cZ;e5^P?@>L!*COczj{?3f(#G zOIW%uefTf3cV#g98*PmR#{ElP#Aju;xMyuoE|GO!7jC`6>n|TeP=np=O0C|d8x3k7 zN4`6>f??Tt)ARbF;!J1XuFqfcido-s$+jbB*RSz8#s1>GPv^FOen;JxD4uMtn6BE+ z{Xe1Tw$sH8FW+4}iS zGfdyw?4}3m8Wv3Uaqkyv{gRUmU-$?_4Nw>uzLNcGIvo-b>Y~kaJ-2Ll=hZq@@|wfL>}1!;t?J*pK5Sb*y-gwb{$>BDi5lQ-!0piJOa}`iT@4$&L9>epSQPg+p}Pv&C}~6 zMCSxN?UeOm=g53_`P!DQLT4#$kJr4o!sD?8T5JevLL(;KNhkN0;62kUX{3(MK;EnpPq0X829@%(p(nHPu)Dax8Vu*1;=%7 zrh7B={fbTuNoL=FU*O({)@?I4eH?#QbqK5EaUXwN6KK-x!*^O(zPvX0$nXJ=&9n=c037#sCd@g$7 z(O$ih`Cr~NI8Wd3F5|iVyvlmYl6bj~ob#T!E%?(JDf#=8j_0quy&o6;y&mae z>}y%FvwGQu7}eS7OlHBSlJ@>wtFpf7_F0vU>(3-?|M-((Ug)*03I?IQt~?stTqoBQ z?S8b^mhGgZ)H|Kcac@6Vv>J$hY3E5@>mQnM*yC94?FZZW^UqFKXFR)%)ncio%d*?S zFIZwSxv%xq+&Q?c>eP{^Li_k1s$9D~!>8G95%1w_Y2#`9?tJFYrpp*QG_0~-bZ6ye zlfIYxQ&<_I-(Ao9$6S4*lmBrt+cm=$!FO_LXAJFc$}C#6BYlNLwJ#R7MtkAJZbXI+;igW&VBqH!Cb}*!{IRGjpHB zJBA=Bo;uMjSF_{#qTJN?e!ICpF;TUG5ds1 zM(%aI)iE z|HzkISLi+WB;M|`*u=%c+yNXbJ9$=}%klrFX0WP8K3nbrJ2NuDEm*7C4voHy83*0^8Q`(^K8+_ULG7x$722iJPd zdU7m{X^ZI={b}01_Y%&mj;&#Ae%$*)P2yK2{WWnTP>9aex zMey{(%%hSm|H|}UOZ{Hkm?A8d5Lu@CG|2eBO3H`iz&&efLccM1w(^<8^EaK}E#xQC zHA83mTBo=})}6f;4xSTIq-WS^=0sV&*O)APhxb~~8fooMUJ-Y+1lT5z@{+a%egRUnwMw@=nPMLUq~kso5{@wX81Im=zHiyTx~N@Vk%Q`vXq}oLljW z{lv99Nv=K{Ivxb%c`?0M>s)Sfg?UthiPWqqlykI3C< zlk5ExUod%}vvuZOvC}Q6S7^rp&e~&)zw;j3UPwqQnqoEoVAfS@#^5Wne=U0=bJlbU zV@EenyVi!A)7QPa%_Hb>V5ZDkW%CtWDb|1g?M?ZaY`o*hh3_m|IhF{SoGvT%{nBx1 zouEOb%Haj)a}A#OyP3rvzGArSSl?Q&cM@y&**`s&RN%PyU{>zQMK>)Tise)^OqnxD z`|_FIwME{-PhK31;uR@roV~t)NoMAKX;G(Hf}w$oXWy!w{`MgJsO1}$i>7-QxCbo% z*im<0MO>u&W;xS`oEaW3gigHIejc_oM82f3EbiZrRM)=JEY&y*ODFMbUEghbQ`9Rs zI`0MSY~B#IA*uD|rwJiFsm}QZUuGz9i`T!HzTryd@!NbS--<>Yi>S<;CCNJV*9N{F zzKowL^m&D@iQn-!`sdB|6fPN#z5ZfTH>(M_Uz^30GW*u??8z1moIyA0T$X9ZFL`m? z*Qo2IEo+qD0Xq}cy)NxXYKyKU%)N6~ykga+v>U538Pyl)z(;XO@Mz`p(=rG4ey8rRavu?-$I@I)D7`?z@MsHqKqT;`Kz2gKzm) zY*Bc;`g_lv>PJzx`qE$K?RxNTO_H+zb;j=7#$u)?E_|LLIKyheVZp^E-Tr*RJ8hSr z?(w~~Wg+`zjjfg%!Ccj+b(Rawt%)r;A1ag?dGGSl_4l^^c(`Zn4Q0msz4Mm3^Q7I4 z`&E2gL*RUU`MK{W6;tc)w^T$fPFVP2_t^uCua_?^ePH{^{}P+R#FB>^g$@4;j6{xw zbw-$Fe=HUJ!5h`~{-$qz?83=P(_%_r_(`_?zWl%3%&t7Oxt48n;>~YLmP`>I-WQJq z*0$bqFqqJj$sypP`sPJc&8xS`r;bcg*E2m{Vq%w;-`{#suhWzBMUV3L`8Ew#uKMtN z_gb)OIR`6M*?&XV_@8R!$MEigJ|BBf% z6J{Kpa3p$#>72@-xs|#boR;{ntc&#cwz=1$Va8VV$4?pW?f-AcseI;RpGk;=D63Ok z|JS}JrZdBRwp=-Q?={Dvtoz#~cl-)ax%optMMd^X)$7j=%hYhc@fQ~h&!IXEL7Cij`n@O^8& zD5`KGWAeu|9f9e*7O(22^6ZPL67b{w$oKKPi$neY`hT-`FEa>d)1S7pC4GVQsmdw& zvLCL9oLkn^J$ueR$1R0lCjPFQX7a1+X3NuEmuGq^y|UeYtL)a(4%=N9OKVuR{%5~F ze^ZDfhim^zO^#<$9x_J*GOlFhh?#v>)L+8EaXG2Q;)s%Utno(yscY6#%+d; z_gT@vx;k^)0;gRG$5nOpc;BtsqbdCIvv1AYOtF$!zE3Rf`v->h_4tO-gCsobhA&wbUOC+szD5KHsUKmdZJ$scV*kzMf7@cUIo*$mYer znVmk?7EZjh>~sEmlTDgSzE8P&TIk`He*7eroGccjYVZmj%6Mx8mF+n(XPFd27|xa`V3)migMZg1%%; z;aYY3NDM!^3^8XE8JXQtvOfypG1)Pmj#P{U+YtbwHj^V)L}BRY^f-wL4EL zKT2EqCocW-gv#|PXT?9STqn19?`!dpxKnj8EJd}f+oylgoxN(4opiYbyUELKUwbcH zwGdkGrSeuVymk2-;}=W5pIJ2h$@b4~bN`%sef!SSN5Rz-UQQHoyq8j{awANE^^SYh zjc1uRJGP4M(v1k&r5SW^hvI4<&D<9~rxskz+rIyCro`tN9WgSm7k}_dOXNB0^LhWl zAC11}WF6RF^9rRL3w!kZ*_~%^BvR%zIp}2lc@vkRDPq{arz4K>Rpb8Z@3S6zI#_#^ zy>$E~ckJ~{3me`y(?0MqEj(l|wu@c#(!8avNq=VLXO+$5`MKcYoMUhF5G`m=LId_4H zAJu}=xt*3&%TM9Yel&Z1vlZip?>iJX7O3SJnr@aTI9Td1c{a<1Eq5mH@Xo2Q+|2KO z?$^ds88%XUjke}9FYjhNBQ7q~c#-M9Z1Uow#`BvNt#kirdn`*o>h9`4I&;{x1I>QM zTujOOxXW!*z_b6cdzIF_347;SU1_emB;4QVR!rtRR#P95mj{Ahe|bBj=G2GyOO@*) z`IfR>UbC)8^p&#f&%ajFw**!#oW+?g&SS7tX#M^VA7&ob7Rl5R%U6$3%kt_pKR>na zsN|8Iu?OBbJ)Ge2I>lm*;hP;>Pb8e&5ESYnaB%yg(8)U|ed$xlwe7i@uh)H^VOl`= zWAh%oNm5L_6{AYeK zcGW%gtB`5et|^LfvwR*INN$TgG1FwZl+Am)$gVXL{1@I_FyVvajX6#;daI|cepzvT zPmBF5?~KsPEDhm$9xo&R%OCuEX_tdm=2I!L)_ecBo0?|-JS;5lUO@c2~e(215b4#dc zmpj+2>3)YpmF51`$|Un%oV3=+?UBU7-tLmW+kY?f|7KcwlGEOXMi`tQO}i*{hl`gtZC9}lxpt$~irYGWKHJ8rd7qgBE+3xt>u+wQs&Uo4cw4yxC(q>Y``)cO zeumRMW z*xfrtM#YyW{4RLPfLB36U#RT+hC_q_WawcjwN|+cOUaZ2i@g@XDmQ zP{yLiGg2y7bu%l+=5>PC64wh~S^?p5yO+xu?>v$((E-a0+WAW%%#M#MC{BzlIamiLVW zbH=uH7nKj!Zkv<*RVYJpwZ(^9dnI~u^|`s%3Dk+OO$!rUtZ@E+ye`Y68;cicG?+D) zv?-XTb1t;*@)F#0?)d91@*;D8@#=Mi`T8@nruA1wamsJ6-5mR?nf>U+6Or@coh&5s zC4Oy=*=eiXf8$>f`{z{Unco>S*6h__@0D7%F>T_ydAlq_uY4>yTJ=d|)+4o7@1`Bt z#;VMF(`V*13;)tJZ%**PSnMO^8fRMZZ_AyvCmZz6GJTRc@XaCIO4fM+o1ay{@pY#j z^0Zs43pocnE8N@Vr0w=(ezMPjyZw#Vw>)*VP=1hcZ0C2+HUFMo3=h_7*kAEz)yE6+ zK8HNmBD>e@RmfEgzP3Jj#@yrQRaF~yvQ3IKDmQ3-)w+L%=)LO-b?%PyxEC386l{1^ z6t?5i`~RH&DbFQUT_-=;vg+N+;0Fg^X2|hfnEX_?YSvPtX8&s$SGZL_zZd+fRk|$w zO76ywaU8{YwLUXE#6R-6T-8jub%EnYj-)^KN7|>{FHJ3K6;*S5$QOOlL(?km zOY)qp54IFo7~ecoE43=O^vT?hrL#Xyh_(w$J-Ao9N8(xZTVLUaf5N%>vJEqNW7ce3 z6XLR@*J5gIh3~0N|9PigTy@WK?e*HFcfLJ-AR^~^&FIDNfB)T29>3!9t6#ZZaLTfE ziy*8f5_Zx8)= z@XBnK3wMQ&?8@NYltuEN7&YBjmqm+3O9i-ZVGZy(VtnTDq)bo#RV#u%&tb?otv+*! z+vz-G+1ItQzwf(k3uE0gCBMJ#Zt$!Z3yazM{&TajvhUEFSS*!aeYxR|)-u1hLg&(} z1k#`6nw(Y1t>W#eEH%<fAF8DQ&oQUZrNEG5`p2xm5CFUPV+r?gYlg5ska}$ityjvGJ!Gpx%qYD zpOSH4HkC`HY0357MiGiu2fbE1pHQj%Dt317y6Y!8w{HBvb~?m-EIUr#mr|IVxXU$3*7V$Tu3t~|wMrMJ{x|IHR9*PXmZjo| zh}-s)3w7$|rhHPg*nQn*8GGdg_04fd(w?pjzCZu_G=HO|r}Wz{glw{3H7#!W$snCY zJF9)$#b%^zuzww(T=rb(lbPVovc*`;xaBIo4l&wlz$l)_0Hd9#YOa;WR%w z)3CiHBzK(+!}C<;>V>(}Be~inOWJuCWxK3ix9#2To`;s6j#JzwGT%g?vGwDkFAyqvo7V^ogN-9>^W>l0*Jws0TJzt)?! zYemtNhEp|LJd>7PGCCr0v%%)+s=xUWM{lUSS9L3%VKT9jO z6lGhB+f^@3jyHT`_Gn#`OAmMPmxrqJo|XDky=x6#$nof0@|;9rp%vVlrwiU$yFQV< zrf~kI4z^d!?s~xo?(96hbA`c9{Tox1`<0y>bs1KA);T*SKEEX=x3EuP#@vPS2Tj7t zleyVy7QH<$VvueB&N1VB_>OhE!qz@z4@*2XGrP@n|I?z2hnsV?*NfTq*EPB7`5!P` zJ6-)|_?F3^W4(ReuW;2}teaDNW8PuYf@z|2l66YbrA|&yO#5>1=CmfmYTlyVK4Ses zyX5>H)xMftY0&%df*fmc*+oFA80`I?PUk~&-8x$_ud;Qz7KQ`_n zPy3BuOQ!t^-#T&EKji~6vKLr&OxmL2wo>^PkEgiBsTI#tm%h7j@8qwDgxxuNKS}I) z-f-&d?hU5WZ$nfVCaqEnUA}zcFFpl+Qw#mZt|doj^Dk(9KV8fBymE4bpqzPyx6aA( z5R7?W=#t7#z3ozJK7LG;7Ajt>=Dd zNVup5|M$B1SUPj}Qx7%a!m=g>Wu2eN0xizZmPaj@)~GtQ?X1O|TR%@1lRw(taZl1B-^FXFZ|?@C7-66n7_SW5c1~JwHK{2E4Br0{}aCLW~D; z^OQCzy}Qc!$@ZMfmm@6?4ofBMw3}Pc-k9FhWNW||ZhJ?THR!Fj<7NZD#dj88n#pPf3OO<+9y_+mPj>xq{QFQsMIzs+{7y;2q=*nVu%tUu+;m!4jbqncT>+p=^Cr^ntY z+%wO~F8LVl{qs~y+>SF_kICg;`jh;@V*dTJ%fFg@ZBUFjcw*Or@EhEf*$af{&EGmX z_uW6GyIx@ zw|LYnvJ-JHHe72Q(y%?O%)?)zVR_8?SuOhew3T?*d2-GcTh)7DNodzr=ZVqvoD$P_ zC9iCHansfVmTt?_+ppT^TR={2J5M?PD4h&_><@ZVlJ;Ht#OL%yE_ z4*lR(Idfyi8o8vuMbBns8_jFJ8vJC7r4ozRPlXrlJLT8^O_w=i;|;_lS7G9@3P)3pVk z&ExQtP+gbkE%@w=+7!2`?y_eRg|7Rp2^K7W=4j{fSySQmWU<|*&8MA??CL)=?c{@n z(ofdDo$WXH}`Pg*D}?EU#_%-P*xiEqUR@UgfL$cDe?}PTOZNrt&=d@A|T9 zd6w@JHkrq^A5OexeYNIH+Rm!R0}i{&4nDvCWwWN2YNLGBPNnd})9$amDW!F>KC~%9 z^Q!fRjGHG)lj=7J{yp}5Ml56a5sj;r84*d2kDfo$x<89KVdj^p7u-P&i*gUR{QX#BcHPM`eG%k;>8=z-nR+%X{)d0wd!hT!fp3dW?tD7K z+(9KY`RzCLKZVt+yY4@EZ}r1B+z-8elzD?_@5VZ@|19S}ne3CB`^WB7oqX~8_7_q= zYCZ3_UHBKNR>k*q_X^@DbnQ{iEYyftjC+$+@{7E^l}&(I8W|`i}H%m#Ig?D(C;@ zy4D}zy5-!%zkhwozJ+w{lXJQLe9?;;?zfqqEaPrn`Hj2o+<%83_v6Ka_pjx=FliU> z=8vANqU)YJ8u-Q+Xtav91RwhMjZOcrrnbo~Iac2#WnR5uS=v+F&o9$m`sefh?xjc7 ztv98Vtn%|;DV4GML@GDO_Cxc(X$8E$qp{^drR$W$exK@b9x@H!w=^$H35B%z0ppqpYqT6z2PuRcKSlL z*P<(H^C~quCd*BZ|2WnCt6Rg~K)WfP4o^k*%v~$G@|}M{T%?}jv7M*B#5{T3FSf^A z_<_o|)BeSlg-f562up_K?vEDGNV&$8(3Jd?!{hDsMsrujO4X^0*ZVhzGkTq!xN&2% z`UmIUXQwZzxWAaD^x8PrcRp9*(KGN`|OUpDrGKE z4RDduOrLx0#73#E8LmHzRCa2_wlO{PN{ODHIe-3*(B)@sra3MA#T519MZL=YgGTiM zEE`^a`KGbCu7%&z^5mAhe7{NeAIqja%;%I@+j+NQ=c;W}n5HP0sZGD5mR9=9;!@T0 zsjfckrA01m&)t~4vJPeUhVNUWV=q?D;IrW4H<`n?Y&a{cFV3F2anq7%6Ql1)v9>JZ z{?=ml)H|TMru*u@2s!n+T?Mi44qFF!zgeHB{G=(;I{a$7;*+O&UW`9}-k6yxcx*n~H2>xScmC7<3ME$Zcy^cFNye09;xf?MQ2UwRXF(e7IrU3`VtCfxk4dh&Q% z%+~s)rkZTsURjg!y(VU7Zn%7F$tCAUL4`l&G>e2yP*nC|5?6X=bGZ1-t zX}|j3edbz$=zVKs#ZJk*eW7{xM(TwSW+%OlZI2@&mcAC-`r({SKl@zk7V-NZyf3bD znfK=IiuIbm#Xm$bJ}b;N`>S=-SRBRvPNBh#}dbz*P`ETGT{%T#j?fVDj>yIvZ zudg$>!gS;Jx_y4-BK_Y>rGzXFt@z||-``~JQtjtbY!Sa+zk6f!QuA!<3*Yxu^_yx9xt*$`EEY_eIB=>r0kaRMfD(=Qt!_#+LKAo2@xz?oPh5taldXvS}WN9PCTok;LBp><&XOh1nf}z znAPS~@m)wW~r5s6Sa%JwQ6sx;(FWF_6Wx86+Lx3am6&LQ*xr_ z37snoW_|m>8|hOV-__L+a*K$miO})#POb-%J-7v!wu2qw)0hPBKJy~mu@!a_;6iXX2HsoAI`aA z^Dq2auJ}_=>*C|C6kmqs*Hf3AQ{8tg-r>vi4}SaGr?35cb4AeS=m~#ZZ}>H+&oz%I zT*9$^71NUoMNV1W-xtZOvbnSC*sGtHx4t!QwXBxrkGf*I$z#I&16xl?ZCqot+V}08 z6R9;SZp)q|ttpyj^WeCuvGOC23x~LPe^@^7bPSU{sg=nb5;yH9&kynN&{x}5U-5eD z5Ot^Yq=wfV#ka0Ym>ve4Ne`B)h)6bfzGuNR|MA0bd|MqOwk@36>8K{vly4!@yqdigO`(C6^j&m+2jLir2in z_{&P)S-VcLG*-`^|4-oK(U$4;(XINdhqipasoWK`?r-FyeV;D;+H~@uz?C(|>8r%! z&L29jZnr}6pv1zd3l`4fk$c{Y%UjD!X3d;!+Xy>BlN04ip$mIUby}$TGmNv_@uG%$ysFdu{fmr~j9{ zD6o5%^0Dp+qom|61JNc+p7-Y%MMG>izGq5K;dbr|vD~+Jmz4J76sCn7hu&KrliMoI zP`#LYYyX2eH`nj*ek!+Ur_|2E88bFli50$Pxn6VfcZmMa^RG4?&RgF!+wf$|hu#U< zrH2m8WAS+LL|Nfj7t5tT8V`#99Xsb0k-n_Ef9>D@jB5kF-8HW?+f#AW|8H@L_`^t* zik5)x9}4V&YkxTZTy6RL$8}xpjZ9O#)=sxhS$}HsrCqnrOFaDahkH-*qZy*NjoUN* zrvKS5_Nw-Z^Tp4*Z|z)sXi8D%fe@cY*_&n)yKgr=*z@d>dQ1}|U-{)^Ds54X3~_qQMc}f^@O~eAGvE{)~|P>H@|;98p`79V0cA- z9p{~&HNPJp`w;wnzEX!~%SyI0{^r5@ISr|cSM0jA%BSPrcfRlf8C9zl5*uz#<(5ga zTei+g=e^{`y(@|)u<~5|syS`>rHO_4HH}Zh%fDXAoFKWsapyFit?xcgJGg-<^r6Y^ zf6VKzJhz&a!YH`p<=Hz`H*H1SOT3Hc&)8 zmQJwI+7l4&=Et5C^WEd^`Ae3U7W{Gh@gm7V`6PQ!TF}~zhpS#)sM_7hnk$#G;_~^F zmlJcYJG;E?H2q??y4a7?;ERv>qwBRU9{%UbuOxmEJ0kPsYShEV{SU&;ix*k$7YKPh z$>2e5QImt?!2>)jcQ-scFWzBtqvmhi(s;IQaqheSbpFwgoHTXvYd-zXpd6KjGfQN0 z=0?@%nQhKaHOgJ~gGH$zk`!&L+E^zTA zU%BVv%f|U)vR1;&WjrD?w=T}9k-Zhn^ycvOZ2QX*U)tu*o3Z%pGuh}YP3MV>r#qfi z99=tW_XMw1hIkE0@L3Ke_!rgT(b6saxNMw>(qU zzL6hOv3K6Rv+g@3cg5UDwmvs)&Zo=QZR3mAT&w=;%y~UXecfY0`M~2M3J3IzCs!{0 zStL{wt-LNn)H=cHC!^{Lr#*|6;(q_gs*|qHOk4ih+KMCVX1ij&;&z=K9CjYDYEAq2 zBEI(jx$)srnddIuO+2^era$mF{wwY0{Hu)eB7gqP{1_A=_LbFpa>Ih9?h9kQs&-bY z3Pw-s(NYeW^KMeJcJ$G(nhd@r%xrGQ7p~%u`uQP^-?r$jaoiKd%cX${6D;j4T}79F zQTlx0Wt&B%rrL}T=Nob@+Lf9Lk1@Q?`y^};p2c`iaf9$j6XkG6&k&-_ zdX4$Ztd0Ydc0XLXIjS`yO6zNOQ?Rp{CMoP63Q zC8a&hZ^@xWryJX)A4RjQo#werM6q5~dT~~Z{Ot?+domVQXXxyxe_2{G@6XnMCeSaZqi>_=daR`^JCYSd1Z_86z$hdekFD? z^^{U#nFwp^QhtU_PwjkUe}3(MIdw(c{MQ|eKW0R3IGHY$zBE_KBW8Bt&8ebOVyA|* zG$#HldLJQ}ckGT+lE2%Y?)vm=DfgA0zS)*|#7IviVn4f{v8vr6PUXXo%v!TsxbxR+ zT=&aud!OCcF77s^yTTuy8tczrAMGFZzxI)o=>Grp|LY#yJiGo{W5U!gtq)#{EO(z? z>%neTBXaOcn_H~PI!R{5Fm}!D8n1i(8v+}@oMrDX&dZzQ{qEG0eHAm;pSaF*SoEJQ zd)4We?^zrSY-N^)Gcj|kCce9MYel5D)A^c+BZnt0mwG4SaVzwjLW07*^Ctp~?lR9k zch6(m%lFYX9so*BaG`9la#W#mYUp>JqxxH{7*-Vj z&i7M0Hp|kRc}<>$$fBqpA0zV$@+JRD#xvcX{?jj2EVyawYny(Sx65_}^9b{n>fQ+7 zYTnTmBeD78hhTrEd7MvAwYp3_HL>mK{jXJ>6%(#pCVIA!P5s`}joeKZWpc`A zC(k+leNK#8gM@r$ovq#yUcGxyCabM-*gIX$b;{IW@ro1w+PP;g-4-WUdx-su?Gm;2 z{#?O18}D#_-G9qr{l2VMK}V-wFN{5EcJtHCq{m^pKX-Y$GH;!%V3fJ-OYsTa{~`hh zKE<*vZmn7N^Za2K=WRP27i%IrI?oGv2)}HghJFZM$?Q^B?Ez#AWhkzLonJ zKD3%u6*9qgj{>D*x$#qlt-FDf3QngsE zEoLq$e<<+pj;_jvQtowRexO$n8*#c>+yB8j`aXoqI zx643s(T(H>^XKl_aBo{jnTF!N=`W*xT7`vob&9dRU(q^y@?18Z^&P>SX12~5#hWcZ zf3H0hCT*h{?CklK|DSi`zxQmN{l`mM#aA?!Smeq4KJa2sR9WBUg>JLoTTOQ~D}MR0 z`9hQ3-qxA&kHgvuRveqSip}d-vuCrYnd&CC6aPgIKQu|cAj@cM-zv?l=p-yHBgwe* z#doXPk0O0*SD$D+pLB0`%>M|RHmx|150X0d+?!JLWLjR!94#z=rk~2<&YPIJ#o=>8 zZ76?yh;Hzw*E7~yzWSe`6fAR5pi1nS%sm;0UtMK)LL}QX{a7#T*b=ovd>-ei%jd4? zq@SNvp0J^yh5OIHdvZR$+q4q4WCJb^C z=3nM=vHksB#@O54u$wFL*y8(bLZ7z3WzwpDwZoI!@|3;Aq@uj96-@^PE6V;W|KZs2 zPb5oMxZLn_y3Mv@=dS3kczo!}Gj=`?x2W$f4X6A2`c_m+WjhOcGtJUyQ2V@8Xnx8* zAAK3|H&?i}o{+4Xy-4t2m&?Ozt&$EuV|a?QP6^6BY&bB-@?yh;%J=^pU-PbX;>urs z#X#?3tFMW-O=j2rXFJSP1M1gZ`!l6<&%@mdT6q5SC#od5{tMkDm%y&EK|4K4=95o` z=f=;x^-~UBJM6V5JP-p-XK;h!TPFG*7}eJngxf0b7BM#ms4#eI*jo;V%w zEBx2NvId{#a>;f4OZIH-oB2Yx@iEUaW!u|&ALNtG-e3H+@`04a#*=0<&t1D>SbJm6 z@y6}kiFfWwoI2~>%O)}Jn3{R}fg;DZ>G^q0wsI?!XWj~!x1(`^d)sl#iW2ALvz%u$ zzn16{-&|tus1ozMH_V5{;^<}Bo3Ym`O&{mpSG~E)MU$&uBstok#c`5{f!*n8diFP? zs+P%FcRr6#J}9-E@jC-woXA>7wJl-EQ8NXPJ(Y@E7U*in_-aW{R894?lU;W$PMf!M z3amYNK|%9?NBG?DF(o$n%ifk1&NNm`{H!eA}y3Xg)Smypb!g%A}y8R(B9Cbp! z64&or?Y3mb!DS&a$F?&s&|7)5Nz3Ki?C7N4q>|y!KBuk?H8d`fcxvI4-(h+?#N6Cqwz;M{aRBBI)`b87phM zr*<;mW!0;yi=Mk5*!n}W zLD^RM+=O_UvuF{}(^BwoXLY}K3 z&wiU-3e#C&#}rR z?yk@I)0S^)jP@y-UOHsPQKqnhhx_hQ8MjF>ziyWXFgPVITrrccx~l$T{L(}l?&yWL zTtmH9uK#b@SLS|1clp~Z)o$8-I+n*;(=X&7<;z$vzw%(imEKt{Cq7i|4qEf0DyM+m z(tn+^%;OY?zu$LlvRE`XdzH+ex4OyqTZL}qJyo7uCUQnQaa#Tqh1H7HH!j^Sz3{Br zPJvJ4O7QNc1wX|*XLRU_e~w?UB|gFZ?9Q-Xa%{<$Ww}0_xD=(hUD)7vk)kbUgXR=P zNs0BD(`62e{@myG;epupG#O{UXDe;%pU&vZdSRg@ec*6z=oU%IPgi$6xnB}+YrpvJ zQ^Eh0)s&x}=N0pl@fKoVWi`J@X2O}PpU=#5Kk-lQ>s{{Vt*#&KgtU6sbyd5>&dIB) zzJF)7-yw^Ag_3vn+_k-2=lSdPVcV~Y^6jyw4hyen_L|;%GE6t&Mfj5Svzo3lPMD&2 z!l>;@=JU2gA)dmqb3Su@`jilVVcpj*j@fBZS#izjvr0RbtW3MnWox+Mo~5D8s?=~E z!8;cddBQH+Rz-hOKM)eKN#Ntk|8Z|jrfme-}3)C zx5Mji;g5AG89~$e7B72tY{7>kZ*7)c+s`Dz>#nqC2Rr}!qTT#DeZMT{Y%$&)lm7Hw zPvZpzJ7Cp9#-J~^^Yr zXZJksmU2ejf>#F1MZdd0n)POWV(2T+508J{c%s|j?6GJ!Z|8m~Nq&zX=6l=@sr%l|}6ISKRWO&l+ z*J&&5|M|{Y{5`9gX1)5oSTssy-eVVge%{?lFjklEQNH^5|GJyjGqjv|pTsN?|6}^w zAI%DO)?0Q4E}d9>)M>kb{T^NW_5OU?Pd~r$Uime{V!8I2`{~BttaEhl%5EyV-&ho) zEV6UPBrE&9%l7~Nr+X^0aI=xtPKRGMsjr?2MX$du{Yvra$v!`_v;5Wm%Nw#2KJyDD=)_BVUBejfA4D z8zbjUvHE(x$???_kJIgUUq1c&C9M00bI8wMzVXZ6tTsL#yYf)Rp62U&&$1u#t<5ss zf8Fmz;dJdI5kG6pk8fSH{^GSm&l0NL)t271ow+7`k&yiU7tKj#aavW+4{&66oAsYL zth?3Yy~OH0`}wTro{Kf-+HvzsxlgHogZ`#A$`e1d*rWlXr?^-_?dm$ zzQesw`jZ21EfYPO%P6z;ydcls2X9lY8D4wLwR^~vo8>;Ov*VWLm(NFHru?~jm%TqO z@4&Q!qGm@X2IjlFo@UQXZCn(8^jf5H2^;I~`tp~qSEAmnKESa$zAmBia>bto-3137X7Xk2 z`t`5;_~9*w>Q_E|A^Dy)cy7nP$SHx}Tm);G_C9@mD7Hdi^^vO1j&*nRI9@kdWpI`| zt7In|F8I7{pYw*(K9jvK%+Y9=t9$(v=Zo5T(ZAC*#J-zVy*`hr=gBPg?#w;2q(!;CHw1-6rn+uQ{*&wYK%m*C{7MZYE^d@N-7Ic|6-ZMOpj(>8&x9 zb7rX>xV6hQcJ`YtrriJpFJo9$#x6S)%oW|`~^mO^_hCVT; z`vR#2VXJf(Em?g2@|~YcJhq>B+SssL=9c-CnmrEbf5OyGX?;1t%HbWmtX+D2i{y%G z?qz3uny0M%W`D|2DrMH1dDoR{HB;}K_MK7^3E25=ddUmNwVI`V?wZ>|-w0@}yq_}X zSBpyj0;kwd#_#p7PZJQ{(k0#bR^Mc`=KC{}w%6(0pZlQ3lXIbvuRqt9jg{ZMO?8{!x=r4{ zZ1=}U_J+yEN?z+0W>42R@n5s@(v*7V)Zc&3>)*@WZL`eQe@%GCCx+4s=NHD9zuc1{ zWUJ20<-7axvx1GD8zsD_uejG{>N|}!JapC(L1XosYvnWa%erwYkQk2`{(#sIQUYkcrQ zwNw8cRZmY5fs6TyMa!&?>pFC;e$i^ZsiUj4`eVECv5kS|5j9io8r}7ya`G~c-j=z% zTiy6R$LyUmc)4W{hi(3{$NK6U?pr$*@4hLhIJok(-7Cvkzhf%4HiTGowxs?u=cd}uy|;N^`>H!mckIq^>FYgE zHQZ~aeov`aG_|Jd=PdqNyj8Yl8EgzuKLmvG7CzA0r~h1LkD~WF%i05sh9&)5uUzZq zV)*)feuU}ONt5oanh?A_Bw=$xhst%KqKlo89~RcVN42w-(!d-(U7rQoiRwnY~s zCuOYJlV{|7!n$NzjnzEIImg!>hi z=l&0>Kc0M^{qGOAoE#HZ_x=^)=c0D6?ef1-{xA5jPiXa#fBP?GZU_ry+uP_;+E^>j zbGd4+S>js5E4|0}rSB;cyHeqO;Jtfmrq2hRPt`kq>~^UBkZ+~t>mX;L`rr9V!`%9O9&_r??stx?Sgt7X$m+#^|Jl86KO*uEX&;(+PRLe&x$3>{ zoze;0%+gk{dI+x8o4@yT)q9l$ZidO)X;Gq0GgrL$y|P(_)7Vktv`_chC`;|s=zyhn zSU)N3xZ|n=UUU~J}H`i{ZyvI4tr-u*4XPQNrZZ|oz^34B&$_vwe zhXvaxUJ#THzV_<;si#%z`vk4}j>|=)+kN`D{z*Gy z>MGu)9QW{#m~S>eapKJvZAUHs%v38B(^TkXaq-*yuUNC?*7o|S=A|u8!U=f>ww30t zbARw;csRdW|KU<>@^#Kv_I~^)9hXQ>n0?N&^=A&>%pKKd;{P2M+mV-1n0k8|SM8)v zeUHw(I=E{~amq^xWGE|M{pq+}!=@=C3TRc2ASp#~qkVe>bj;QhA|2)kpcs zY}?(=)9ZF+X#ZBI6lmJhzjUF9XrR62V>f55^*SZTXKw#8|NNuZM_i0Vzi`#CvR?kZ zY|`WV5vs4GQW!r9PWd!rt-{;7&&y8t22b2_pCMFwLHX8gtV@0wc z9QVv$>-9oDDNFpY!mjNmon9Nyu{AIB+2bpXPL5J@ysvALg*#3SazPLp*t&|98v9dXvuugqFFt?SGcwn6=12{zmuv z)ZnwGj`e+-*{|L@wwO-UJE&IIFl9#EvD%YLx;LK}b5BdzSsMDPOrdc{BbWPz6S4E2 z+?*~~w^c#m>g_{6QcryFl5@KFPG0-MuBf--8*O=?yinM(y7HEd;-gBX-UWS{?vKA( ztlwtA;r~bP;BJHY2~zcOK{o5J|L*$nUAutewRTdu_0!z#eEHSW&w6aEIkG$H?S|l4 zYoktQ%ssxgr0=?6H`cA| z`Lb}C@mr@CIufeA-?TrLv9Ni?D#TAeqp~pJW1O7loGJ4*CWdePJZZhhrr7IIsebzp zOk)ajn%8^vf94|>Gdy&HDvw43N z-S#?nT}5Jpao&R7gymnOA07TRDPH5U6Jw!-dO7nksW=sm8GQc@Cd&NL@y>OWKP3`3 z+e7xbyhD*q(+inj);-M=EvHT4xfZ|oO3exNImTRPoD4Ude5tPWw=C`i=N3_yi*+GC zxcTGyYi4fvpDEToDFm;k8Ql6 zy1h_{C1A$085?JvXMS`1p^3pY$%3uwzxS7a^HEowA-wze?w;WEZAnK|?;PHGXXcz` zlYeP)-kcym?X}JIPRWL~#a4YH(`Sow1bOQ(FKc6|Rx+(${O8HzJ%`r*T(^Gc(F%&5>1yHXQz%*}bGp zci+MCnAt^ht4%9PHeY)gZE_(zmX+b$*}9r*{W^cYCEdQQvS6F&!GMPn*ZCiLo=uk7 zZp`nm&d?pNTsOh^x^l~dx+9*Cj(igRp4iR&yD+%4@%TYEown=mEMJGs$d)R~`r$cY bvPti8=?o>mhW|G+lk>trM^!rgV_*OPFXfI0 From 08f8701b33d1a7cc5dc9d92e1c4b9a77cb35f7ab Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 31 Mar 2012 17:52:58 +0200 Subject: [PATCH 59/85] workaround for tga problem --- components/bsa/bsa_archive.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index 41bff7e40a..f4f4b898cf 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -71,6 +71,9 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { + if (filename.find(".tga") != std::string::npos) + return false; + { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -179,7 +182,7 @@ class DirArchive: public Ogre::FileSystemArchive bool exists(const String& filename) { std::string copy; - + if (findFile(filename, copy)) return FileSystemArchive::exists(copy); @@ -192,7 +195,7 @@ class DirArchive: public Ogre::FileSystemArchive if (findFile(filename, copy)) return FileSystemArchive::open(copy, readonly); - + DataStreamPtr p; return p; } @@ -243,7 +246,7 @@ bool exists(const String& filename) { } // Check if the file exists. - bool cexists(const String& filename) const { + bool cexists(const String& filename) const { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' || filename.at(filename.length() - 1) == '"' || filename.at(filename.length() - 1) == '>' || filename.at(filename.length() - 1) == ':' @@ -254,7 +257,7 @@ bool exists(const String& filename) { if(filename.at(filename.length() - 2) == '>') passed = filename.substr(0, filename.length() - 6); -return arc.exists(passed.c_str()); +return arc.exists(passed.c_str()); } time_t getModifiedTime(const String&) { return 0; } From 653fbdd10cf83a3d361b7017b6e491d6df7aa67a Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 18:24:43 +0200 Subject: [PATCH 60/85] master/plugin support; needs multimap instead of map --- apps/mwiniimporter/importer.cpp | 58 +++++++++++++++++++++++++++++++++ apps/mwiniimporter/importer.hpp | 1 + apps/mwiniimporter/main.cpp | 5 +++ 3 files changed, 64 insertions(+) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 09088774b4..7532bf1dae 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include MwIniImporter::MwIniImporter() { const char *map[][2] = @@ -99,6 +102,61 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { } } +void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini) { + std::vector esmFiles; + std::string baseEsm("Game Files:GameFile"); + std::string esmFile(""); + + strmap::iterator it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + esmFile = baseEsm; + esmFile.append(1,i+'0'); + + it = ini.find(esmFile); + if(it == ini.end()) { + break; + } + + std::cout << "found EMS file: " << it->second << std::endl; + esmFiles.push_back(it->second); + esmFile = ""; + } + + + std::vector bsaFiles; + std::string baseBsa("Archives:Archive "); + std::string bsaFile(""); + + it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + bsaFile = baseBsa; + bsaFile.append(1,i+'0'); + + it = ini.find(bsaFile); + if(it == ini.end()) { + break; + } + + std::cout << "found BSA file: " << it->second << std::endl; + bsaFiles.push_back(it->second); + bsaFile = ""; + } + + if(!esmFiles.empty()) { + cfg.erase("master"); + for(std::vector::iterator it = esmFiles.begin(); it != esmFiles.end(); it++) { + cfg.insert(std::make_pair("master", *it)); + } + } + + if(!bsaFile.empty()) { + cfg.erase("plugin"); + for(std::vector::iterator it = bsaFiles.begin(); it != bsaFiles.end(); it++) { + cfg.insert(std::make_pair("plugin", *it)); + } + } +} + bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { return false; } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index ad5aaacde3..13cb02ee69 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -16,6 +16,7 @@ class MwIniImporter { strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); void merge(strmap &cfg, strmap &ini); + void importGameFiles(strmap &cfg, strmap &ini); void writeToFile(std::string file, strmap &cfg); private: diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 059703ea89..87225432d1 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -16,6 +16,7 @@ int main(int argc, char *argv[]) { ("ini,i", bpo::value(), "morrowind.ini file") ("cfg,c", bpo::value(), "openmw.cfg file") ("output,o", bpo::value()->default_value(""), "openmw.cfg file") + ("game-files,g", "import esm and esp files") ; bpo::variables_map vm; @@ -65,6 +66,10 @@ int main(int argc, char *argv[]) { std::mapcfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); + + if(vm.count("game-files")) { + importer.importGameFiles(cfg, ini); + } std::cout << "write to: " << outputFile << std::endl; importer.writeToFile(outputFile, cfg); From 092de45924ec2c263049e9fe46757892fb93c039 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 18:28:48 +0200 Subject: [PATCH 61/85] std::map to std::multimap --- apps/mwiniimporter/importer.cpp | 4 ++-- apps/mwiniimporter/importer.hpp | 2 +- apps/mwiniimporter/main.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 7532bf1dae..a92eee725c 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -26,7 +26,7 @@ strmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; std::string section(""); - std::map map; + std::multimap map; boost::iostreams::streamfile(filename.c_str()); std::string line; @@ -58,7 +58,7 @@ strmap MwIniImporter::loadIniFile(std::string filename) { strmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - std::map map; + std::multimap map; boost::iostreams::streamfile(filename.c_str()); std::string line; diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 13cb02ee69..3c85fd25a2 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -6,7 +6,7 @@ #include -typedef std::map strmap; +typedef std::multimap strmap; class MwIniImporter { diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 87225432d1..5eba95961a 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -62,8 +62,8 @@ int main(int argc, char *argv[]) { MwIniImporter importer; importer.setVerbose(vm.count("verbose")); - std::mapini = importer.loadIniFile(iniFile); - std::mapcfg = importer.loadCfgFile(cfgFile); + std::multimapini = importer.loadIniFile(iniFile); + std::multimapcfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); From cbf6c0404a7f0177e5eea47da04c77beb0c8a7f3 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:06:12 -0700 Subject: [PATCH 62/85] Implement a basic underwater sound environment --- apps/openmw/mwsound/openal_output.cpp | 60 +++++++++++++++++++++++---- apps/openmw/mwsound/openal_output.hpp | 6 ++- apps/openmw/mwsound/sound_output.hpp | 4 +- apps/openmw/mwsound/soundmanager.cpp | 11 ++++- apps/openmw/mwsound/soundmanager.hpp | 5 +++ 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ddf4df7057..6c7fff973a 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -260,7 +260,16 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = 1.0f; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -388,7 +397,16 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = 1.0f; + if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -397,10 +415,18 @@ void OpenAL_Sound::update() void OpenAL_Sound3D::update() { + ALfloat gain = mVolume*mBaseVolume; + ALfloat pitch = 1.0f; if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) - alSourcef(mSource, AL_GAIN, 0.0f); - else - alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume); + gain = 0.0f; + else if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) + { + gain *= 0.9f; + pitch *= 0.7f; + } + + alSourcef(mSource, AL_GAIN, gain); + alSourcef(mSource, AL_PITCH, pitch); alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]); alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f); alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f); @@ -638,6 +664,11 @@ SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float alSourcef(src, AL_MAX_DISTANCE, 1000.0f); alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } alSourcef(src, AL_GAIN, volume); alSourcef(src, AL_PITCH, pitch); @@ -685,6 +716,11 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector alSourcef(src, AL_MAX_DISTANCE, max); alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ? 0.0f : volume); alSourcef(src, AL_PITCH, pitch); @@ -701,7 +737,7 @@ SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector } -SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch) +SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch, int flags) { boost::shared_ptr sound; ALuint src; @@ -713,6 +749,8 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa try { + if((flags&Play_Loop)) + std::cout <<"Warning: cannot loop stream "<open(fname); sound.reset(new OpenAL_SoundStream(*this, src, decoder)); @@ -731,6 +769,11 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa alSourcef(src, AL_MAX_DISTANCE, 1000.0f); alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f); + if(!(flags&Play_NoEnv) && mLastEnvironment == Env_Underwater) + { + volume *= 0.9f; + pitch *= 0.7f; + } alSourcef(src, AL_GAIN, volume); alSourcef(src, AL_PITCH, pitch); @@ -743,9 +786,10 @@ SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, floa } -void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) +void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) { mPos = pos; + mLastEnvironment = env; if(mContext) { @@ -762,7 +806,7 @@ void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 OpenAL_Output::OpenAL_Output(SoundManager &mgr) : Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0), - mStreamThread(new StreamThread) + mLastEnvironment(Env_Normal), mStreamThread(new StreamThread) { } diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index a709576bae..d62d20286a 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -36,6 +36,8 @@ namespace MWSound ALuint getBuffer(const std::string &fname); void bufferFinished(ALuint buffer); + Environment mLastEnvironment; + virtual std::vector enumerate(); virtual void init(const std::string &devname=""); virtual void deinit(); @@ -43,9 +45,9 @@ namespace MWSound virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags); virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, int flags); - virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch); + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags); - virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir); + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env); OpenAL_Output& operator=(const OpenAL_Output &rhs); OpenAL_Output(const OpenAL_Output &rhs); diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 1507e18472..774e42efa3 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -27,9 +27,9 @@ namespace MWSound virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0; virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch, float min, float max, int flags) = 0; - virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0; + virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch, int flags) = 0; - virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) = 0; + virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir, Environment env) = 0; Sound_Output& operator=(const Sound_Output &rhs); Sound_Output(const Sound_Output &rhs); diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 2c2e6e9f91..aaeef80ff5 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -137,8 +137,9 @@ namespace MWSound { if(mMusic) mMusic->stop(); - mMusic = mOutput->streamSound(filename, 0.4f, 1.0f); + mMusic = mOutput->streamSound(filename, 0.4f, 1.0f, Play_NoEnv); mMusic->mBaseVolume = 0.4f; + mMusic->mFlags = Play_NoEnv; } catch(std::exception &e) { @@ -408,19 +409,25 @@ namespace MWSound if(!isMusicPlaying()) startRandomTitle(); + MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell(); Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera(); Ogre::Vector3 nPos, nDir, nUp; nPos = cam->getRealPosition(); nDir = cam->getRealDirection(); nUp = cam->getRealUp(); + Environment env = Env_Normal; + if(nPos.y < current->cell->water) + env = Env_Underwater; + // The output handler is expecting vectors oriented like the game // (that is, -Z goes down, +Y goes forward), but that's not what we // get from Ogre's camera, so we have to convert. const Ogre::Vector3 pos(nPos[0], -nPos[2], nPos[1]); const Ogre::Vector3 at(nDir[0], -nDir[2], nDir[1]); const Ogre::Vector3 up(nUp[0], -nUp[2], nUp[1]); - mOutput->updateListener(pos, at, up); + + mOutput->updateListener(pos, at, up, env); // Check if any sounds are finished playing, and trash them SoundMap::iterator snditer = mActiveSounds.begin(); diff --git a/apps/openmw/mwsound/soundmanager.hpp b/apps/openmw/mwsound/soundmanager.hpp index de5cca839c..cad5f61871 100644 --- a/apps/openmw/mwsound/soundmanager.hpp +++ b/apps/openmw/mwsound/soundmanager.hpp @@ -43,6 +43,11 @@ namespace MWSound static inline int operator&(const PlayMode &a, const PlayMode &b) { return (int)a & (int)b; } + enum Environment { + Env_Normal, + Env_Underwater, + }; + class SoundManager { Ogre::ResourceGroupManager& mResourceMgr; From f8d45eae52413bba78fea6715046ac7ac7eb2182 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:08:05 +0200 Subject: [PATCH 63/85] fix #1 --- apps/openmw/mwrender/occlusionquery.cpp | 7 +++++-- apps/openmw/mwrender/occlusionquery.hpp | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 228d8a4990..b571e3c3ad 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -55,20 +55,21 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); + mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); mBBQueryTotal = mRendering->getScene()->createBillboardSet(1); mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); mBBQueryTotal->setMaterialName("QueryTotalPixels"); mBBQueryTotal->setRenderQueueGroup(queue); - mBBNode->attachObject(mBBQueryTotal); + mBBNodeReal->attachObject(mBBQueryTotal); mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); mBBQueryVisible->setDefaultDimensions(150, 150); mBBQueryVisible->createBillboard(Vector3::ZERO); mBBQueryVisible->setMaterialName("QueryVisiblePixels"); mBBQueryVisible->setRenderQueueGroup(queue); - mBBNode->attachObject(mBBQueryVisible); + mBBNodeReal->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); /// \todo ideally this should occupy exactly 1 pixel on the screen @@ -178,6 +179,8 @@ void OcclusionQuery::update(float duration) dist /= 1000.f; mBBNode->setPosition(mSunNode->getPosition() * dist); mBBNode->setScale(dist, dist, dist); + mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); + mBBNodeReal->setScale(mBBNode->getScale()); // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index e81358eb6c..102b18bee3 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -65,6 +65,7 @@ namespace MWRender Ogre::SceneNode* mSunNode; Ogre::SceneNode* mBBNode; + Ogre::SceneNode* mBBNodeReal; float mSunVisibility; Ogre::SceneNode* mObjectNode; From 909abb480d8479f7f70268f717556cb2f82b9102 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:09:36 +0200 Subject: [PATCH 64/85] fix 2 --- apps/openmw/mwrender/occlusionquery.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index b571e3c3ad..9baea23b51 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -217,8 +217,6 @@ void OcclusionQuery::update(float duration) //std::cout << "Single object query result: " << result << " pixels " << std::endl; mTestResult = (result != 0); - mBBQuerySingleObject->setVisible(false); - mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; } From c08a2b294224da72d32d409c7229aa999c1c4f82 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:12:02 +0200 Subject: [PATCH 65/85] fix 3 --- apps/openmw/mwrender/occlusionquery.cpp | 10 +++++++--- apps/openmw/mwrender/occlusionquery.hpp | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 9baea23b51..de66df6d97 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,7 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), - mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false) + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false) { mRendering = renderer; mSunNode = sunNode; @@ -82,6 +82,7 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mRendering->getScene()->addRenderObjectListener(this); mRendering->getScene()->addRenderQueueListener(this); mDoQuery = true; + mDoQuery2 = true; } OcclusionQuery::~OcclusionQuery() @@ -125,7 +126,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunVisibleAreaQuery; } } - if (rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + if (mDoQuery2 == true && rend == mBBQuerySingleObject && mQuerySingleObjectRequested) { mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; @@ -154,7 +155,7 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); } - if (mObjectWasVisible == false && mQuerySingleObjectRequested) + if (mObjectWasVisible == false && mDoQuery2 && mQuerySingleObjectRequested) { mSingleObjectQuery->beginOcclusionQuery(); mSingleObjectQuery->endOcclusionQuery(); @@ -185,6 +186,7 @@ void OcclusionQuery::update(float duration) // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) mDoQuery = false; + mDoQuery2 = false; if (!mSunTotalAreaQuery->isStillOutstanding() && !mSunVisibleAreaQuery->isStillOutstanding()) @@ -219,6 +221,8 @@ void OcclusionQuery::update(float duration) mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; + + mDoQuery2 = true; } } diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index 102b18bee3..ebdc51311d 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -77,6 +77,7 @@ namespace MWRender bool mSupported; bool mDoQuery; + bool mDoQuery2; bool mQuerySingleObjectRequested; bool mQuerySingleObjectStarted; From c9067249dd0a3e4eef6d5db37480bc74426721c8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 19:16:14 +0200 Subject: [PATCH 66/85] fix 4 --- apps/openmw/mwrender/occlusionquery.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index de66df6d97..2bcf6bd4b4 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -126,7 +126,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunVisibleAreaQuery; } } - if (mDoQuery2 == true && rend == mBBQuerySingleObject && mQuerySingleObjectRequested) + if (mDoQuery2 == true && rend == mBBQuerySingleObject) { mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; @@ -155,7 +155,7 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); } - if (mObjectWasVisible == false && mDoQuery2 && mQuerySingleObjectRequested) + if (mObjectWasVisible == false && mDoQuery2) { mSingleObjectQuery->beginOcclusionQuery(); mSingleObjectQuery->endOcclusionQuery(); @@ -210,13 +210,13 @@ void OcclusionQuery::update(float duration) mDoQuery = true; } - if (mQuerySingleObjectStarted && !mSingleObjectQuery->isStillOutstanding()) + if (!mSingleObjectQuery->isStillOutstanding()) { unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); - //std::cout << "Single object query result: " << result << " pixels " << std::endl; + std::cout << "Single object query result: " << result << " pixels " << std::endl; mTestResult = (result != 0); mQuerySingleObjectStarted = false; From 4944a29b21b515f33686ab54dc0ecfd71e8b6cbd Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:41:12 -0700 Subject: [PATCH 67/85] Keep track of the sound pitch --- apps/openmw/mwsound/openal_output.cpp | 6 +++--- apps/openmw/mwsound/sound.hpp | 2 ++ apps/openmw/mwsound/soundmanager.cpp | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 6c7fff973a..41411a6216 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -261,7 +261,7 @@ bool OpenAL_SoundStream::isPlaying() void OpenAL_SoundStream::update() { ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = 1.0f; + ALfloat pitch = mPitch; if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) { gain *= 0.9f; @@ -398,7 +398,7 @@ bool OpenAL_Sound::isPlaying() void OpenAL_Sound::update() { ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = 1.0f; + ALfloat pitch = mPitch; if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) { gain *= 0.9f; @@ -416,7 +416,7 @@ void OpenAL_Sound::update() void OpenAL_Sound3D::update() { ALfloat gain = mVolume*mBaseVolume; - ALfloat pitch = 1.0f; + ALfloat pitch = mPitch; if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance) gain = 0.0f; else if(!(mFlags&Play_NoEnv) && mOutput.mLastEnvironment == Env_Underwater) diff --git a/apps/openmw/mwsound/sound.hpp b/apps/openmw/mwsound/sound.hpp index ca12ec5571..a33892548c 100644 --- a/apps/openmw/mwsound/sound.hpp +++ b/apps/openmw/mwsound/sound.hpp @@ -16,6 +16,7 @@ namespace MWSound Ogre::Vector3 mPos; float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */ float mBaseVolume; + float mPitch; float mMinDistance; float mMaxDistance; int mFlags; @@ -29,6 +30,7 @@ namespace MWSound Sound() : mPos(0.0f, 0.0f, 0.0f) , mVolume(1.0f) , mBaseVolume(1.0f) + , mPitch(1.0f) , mMinDistance(20.0f) /* 1 * min_range_scale */ , mMaxDistance(12750.0f) /* 255 * max_range_scale */ , mFlags(Play_Normal) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index aaeef80ff5..a00b7dc661 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -216,6 +216,7 @@ namespace MWSound sound = mOutput->playSound(file, volume*basevol, pitch, mode); sound->mVolume = volume; sound->mBaseVolume = basevol; + sound->mPitch = pitch; sound->mMinDistance = min; sound->mMaxDistance = max; sound->mFlags = mode; @@ -246,6 +247,7 @@ namespace MWSound sound->mPos = objpos; sound->mVolume = volume; sound->mBaseVolume = basevol; + sound->mPitch = pitch; sound->mMinDistance = min; sound->mMaxDistance = max; sound->mFlags = mode; From 3a57746ee47f8504f6c531fd43166316ba973fd9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:43:55 -0700 Subject: [PATCH 68/85] Remove an unneeded volume special-case --- apps/openmw/mwsound/soundmanager.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index a00b7dc661..1aa2bf2bf7 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -88,10 +88,7 @@ namespace MWSound if(snd == NULL) throw std::runtime_error(std::string("Failed to lookup sound ")+soundId); - if(snd->data.volume == 0) - volume = 0.0f; - else - volume *= pow(10.0, (snd->data.volume/255.0f*3348.0 - 3348.0) / 2000.0); + volume *= pow(10.0, (snd->data.volume/255.0*3348.0 - 3348.0) / 2000.0); if(snd->data.minRange == 0 && snd->data.maxRange == 0) { From 3cc81d74bc3395198f9fa995191b410842de31e0 Mon Sep 17 00:00:00 2001 From: Eli2 Date: Sat, 31 Mar 2012 19:50:21 +0200 Subject: [PATCH 69/85] Cleanup, replaced if with switch --- apps/openmw/mwgui/window_manager.cpp | 110 ++++++++++++--------------- 1 file changed, 48 insertions(+), 62 deletions(-) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 4654674695..1afca1d41a 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -180,71 +180,57 @@ void WindowManager::updateVisible() // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); - // If in game mode, don't show anything. - if(mode == GM_Game) //Use a switch/case structure - { - return; - } + int eff; + switch(mode) { + case GM_Game: + // If in game mode, don't show anything. + break; + case GM_MainMenu: + menu->setVisible(true); + break; + case GM_Console: + console->enable(); + break; + case GM_Name: + case GM_Race: + case GM_Class: + case GM_ClassPick: + case GM_ClassCreate: + case GM_Birth: + case GM_ClassGenerate: + case GM_Review: + mCharGen->spawnDialog(mode); + break; + case GM_Inventory: + // First, compute the effective set of windows to show. + // This is controlled both by what windows the + // user has opened/closed (the 'shown' variable) and by what + // windows we are allowed to show (the 'allowed' var.) + eff = shown & allowed; - if(mode == GM_MainMenu) - { - // Enable the main menu - menu->setVisible(true); - return; - } - - if(mode == GM_Console) - { - console->enable(); - return; - } - - //There must be a more elegant solution - if (mode == GM_Name || mode == GM_Race || mode == GM_Class || mode == GM_ClassPick || mode == GM_ClassCreate || mode == GM_Birth || mode == GM_ClassGenerate || mode == GM_Review) - { - mCharGen->spawnDialog(mode); - return; - } - - if(mode == GM_Inventory) - { - // Ah, inventory mode. First, compute the effective set of - // windows to show. This is controlled both by what windows the - // user has opened/closed (the 'shown' variable) and by what - // windows we are allowed to show (the 'allowed' var.) - int eff = shown & allowed; - - // Show the windows we want - map -> setVisible( (eff & GW_Map) != 0 ); - stats -> setVisible( (eff & GW_Stats) != 0 ); - return; - } - - if (mode == GM_Dialogue) - { - dialogueWindow->open(); - return; - } - - if(mode == GM_InterMessageBox) - { - if(!mMessageBoxManager->isInteractiveMessageBox()) { + // Show the windows we want + map -> setVisible( (eff & GW_Map) != 0 ); + stats -> setVisible( (eff & GW_Stats) != 0 ); + break; + case GM_Dialogue: + dialogueWindow->open(); + break; + case GM_InterMessageBox: + if(!mMessageBoxManager->isInteractiveMessageBox()) { + setGuiMode(GM_Game); + } + break; + case GM_Journal: + mJournal->setVisible(true); + mJournal->open(); + break; + default: + // Unsupported mode, switch back to game + // Note: The call will eventually end up this method again but + // will stop at the check if mode is GM_Game. setGuiMode(GM_Game); - } - return; + break; } - - if(mode == GM_Journal) - { - mJournal->setVisible(true); - mJournal->open(); - return; - } - - // Unsupported mode, switch back to game - // Note: The call will eventually end up this method again but - // will stop at the check if(mode == GM_Game) above. - setGuiMode(GM_Game); } void WindowManager::setValue (const std::string& id, const MWMechanics::Stat& value) From e8e8d3fb1b4ef1fb917e04278fd6493c310d34ca Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 31 Mar 2012 10:59:29 -0700 Subject: [PATCH 70/85] Fully reset the music before starting the next track --- apps/openmw/mwsound/soundmanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/soundmanager.cpp b/apps/openmw/mwsound/soundmanager.cpp index 1aa2bf2bf7..a96aac6c5c 100644 --- a/apps/openmw/mwsound/soundmanager.cpp +++ b/apps/openmw/mwsound/soundmanager.cpp @@ -132,8 +132,7 @@ namespace MWSound std::cout <<"Playing "<stop(); + stopMusic(); mMusic = mOutput->streamSound(filename, 0.4f, 1.0f, Play_NoEnv); mMusic->mBaseVolume = 0.4f; mMusic->mFlags = Play_NoEnv; From b018d00fd3feff313ec9c099ea5c73be89f24e5d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:05:23 +0200 Subject: [PATCH 71/85] fix 5 --- apps/openmw/mwrender/occlusionquery.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index 2bcf6bd4b4..f4eb08acdc 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -126,7 +126,7 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass mActiveQuery = mSunVisibleAreaQuery; } } - if (mDoQuery2 == true && rend == mBBQuerySingleObject) + if (mDoQuery == true && rend == mBBQuerySingleObject) { mQuerySingleObjectStarted = true; mQuerySingleObjectRequested = false; @@ -155,7 +155,7 @@ void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocati mSunVisibleAreaQuery->beginOcclusionQuery(); mSunVisibleAreaQuery->endOcclusionQuery(); } - if (mObjectWasVisible == false && mDoQuery2) + if (mObjectWasVisible == false && mDoQuery) { mSingleObjectQuery->beginOcclusionQuery(); mSingleObjectQuery->endOcclusionQuery(); @@ -189,7 +189,8 @@ void OcclusionQuery::update(float duration) mDoQuery2 = false; if (!mSunTotalAreaQuery->isStillOutstanding() - && !mSunVisibleAreaQuery->isStillOutstanding()) + && !mSunVisibleAreaQuery->isStillOutstanding() + && !mSingleObjectQuery->isStillOutstanding()) { unsigned int totalPixels; unsigned int visiblePixels; @@ -208,10 +209,6 @@ void OcclusionQuery::update(float duration) if (mSunVisibility > 1) mSunVisibility = 1; } - mDoQuery = true; - } - if (!mSingleObjectQuery->isStillOutstanding()) - { unsigned int result; mSingleObjectQuery->pullOcclusionQuery(&result); @@ -222,7 +219,7 @@ void OcclusionQuery::update(float duration) mQuerySingleObjectStarted = false; mQuerySingleObjectRequested = false; - mDoQuery2 = true; + mDoQuery = true; } } From 5f78f6c72325de90c7efbc55fc191dc3c27dee44 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:10:34 +0200 Subject: [PATCH 72/85] change queue --- apps/openmw/mwrender/occlusionquery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index f4eb08acdc..ade0f976e0 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -61,14 +61,14 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod mBBQueryTotal->setDefaultDimensions(150, 150); mBBQueryTotal->createBillboard(Vector3::ZERO); mBBQueryTotal->setMaterialName("QueryTotalPixels"); - mBBQueryTotal->setRenderQueueGroup(queue); + mBBQueryTotal->setRenderQueueGroup(queue+1); mBBNodeReal->attachObject(mBBQueryTotal); mBBQueryVisible = mRendering->getScene()->createBillboardSet(1); mBBQueryVisible->setDefaultDimensions(150, 150); mBBQueryVisible->createBillboard(Vector3::ZERO); mBBQueryVisible->setMaterialName("QueryVisiblePixels"); - mBBQueryVisible->setRenderQueueGroup(queue); + mBBQueryVisible->setRenderQueueGroup(queue+1); mBBNodeReal->attachObject(mBBQueryVisible); mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1); From cfb194f1d13c1836022b0845d7ffdc5d896a768b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:18:20 +0200 Subject: [PATCH 73/85] fix 6 --- apps/openmw/mwrender/occlusionquery.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index ade0f976e0..bfa3b73aef 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -140,6 +140,11 @@ void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation) { + if (mActiveQuery != NULL) + { + mActiveQuery->endOcclusionQuery(); + mActiveQuery = NULL; + } /** * for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa * this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called From 13efe68fc3a628abaa8ca65181390030983c4e22 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 31 Mar 2012 20:28:07 +0200 Subject: [PATCH 74/85] disabled some debug output --- apps/openmw/mwrender/occlusionquery.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index bfa3b73aef..cc3464c646 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -95,7 +95,6 @@ OcclusionQuery::~OcclusionQuery() bool OcclusionQuery::supported() { - //if (!mResponding) std::cout << "Occlusion query timed out" << std::endl; return mSupported; } @@ -218,7 +217,6 @@ void OcclusionQuery::update(float duration) mSingleObjectQuery->pullOcclusionQuery(&result); - std::cout << "Single object query result: " << result << " pixels " << std::endl; mTestResult = (result != 0); mQuerySingleObjectStarted = false; From 4ef921c43fd82f09e61677765c4394de2c32f251 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 20:50:22 +0200 Subject: [PATCH 75/85] Prevent internal classes from being marked dllimport --- apps/openmw/mwrender/terrainmaterial.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp index 798821d616..3cb3163475 100644 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -50,7 +50,7 @@ namespace Ogre terrain. @note Requires the Cg plugin to render correctly */ - class _OgreTerrainExport TerrainMaterialGeneratorB : public TerrainMaterialGenerator + class TerrainMaterialGeneratorB : public TerrainMaterialGenerator { public: TerrainMaterialGeneratorB(); @@ -58,7 +58,7 @@ namespace Ogre /** Shader model 2 profile target. */ - class _OgreTerrainExport SM2Profile : public TerrainMaterialGenerator::Profile + class SM2Profile : public TerrainMaterialGenerator::Profile { public: SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc); @@ -161,7 +161,7 @@ namespace Ogre void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt); /// Interface definition for helper class to generate shaders - class _OgreTerrainExport ShaderHelper : public TerrainAlloc + class ShaderHelper : public TerrainAlloc { public: ShaderHelper() {} @@ -194,7 +194,7 @@ namespace Ogre }; /// Utility class to help with generating shaders for Cg / HLSL. - class _OgreTerrainExport ShaderHelperCg : public ShaderHelper + class ShaderHelperCg : public ShaderHelper { protected: HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); @@ -212,7 +212,7 @@ namespace Ogre void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); }; - class _OgreTerrainExport ShaderHelperHLSL : public ShaderHelperCg + class ShaderHelperHLSL : public ShaderHelperCg { protected: HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); @@ -220,7 +220,7 @@ namespace Ogre }; /// Utility class to help with generating shaders for GLSL. - class _OgreTerrainExport ShaderHelperGLSL : public ShaderHelper + class ShaderHelperGLSL : public ShaderHelper { protected: HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); From 64f792c01d4e3595f5ba0116773af23b23c7f9ab Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sat, 31 Mar 2012 21:05:33 +0200 Subject: [PATCH 76/85] Fix a value defined at the wrong place --- apps/openmw/mwsound/openal_output.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index ddf4df7057..e57c1a7094 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -71,7 +71,7 @@ static ALenum getALFormat(ChannelConfig chans, SampleType type) class OpenAL_SoundStream : public Sound { static const ALuint sNumBuffers = 6; - static const ALfloat sBufferLength = 0.125f; + static const ALfloat sBufferLength; OpenAL_Output &mOutput; @@ -101,6 +101,7 @@ public: bool process(); }; +const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f; // // A background streaming thread (keeps active streams processed) From 6d875dfd54105505c62f831717632e6c9eeaad12 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 21:06:48 +0200 Subject: [PATCH 77/85] handle master/plugin properly --- apps/mwiniimporter/importer.cpp | 120 ++++++++++++++------------------ apps/mwiniimporter/importer.hpp | 10 ++- apps/mwiniimporter/main.cpp | 14 ++-- 3 files changed, 69 insertions(+), 75 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index a92eee725c..937073632d 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include MwIniImporter::MwIniImporter() { const char *map[][2] = @@ -26,17 +27,12 @@ strmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; std::string section(""); - std::multimap map; + std::map map; boost::iostreams::streamfile(filename.c_str()); std::string line; while (std::getline(file, line)) { - // ignore sections for now - if(line.empty() || line[0] == ';') { - continue; - } - if(line[0] == '[') { if(line.length() > 2) { section = line.substr(1, line.length()-3); @@ -44,6 +40,15 @@ strmap MwIniImporter::loadIniFile(std::string filename) { continue; } + int comment_pos = line.find(";"); + if(comment_pos > 0) { + line = line.substr(0,comment_pos); + } + + if(line.empty()) { + continue; + } + int pos = line.find("="); if(pos < 1) { continue; @@ -58,16 +63,12 @@ strmap MwIniImporter::loadIniFile(std::string filename) { strmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - std::multimap map; + std::map map; boost::iostreams::streamfile(filename.c_str()); std::string line; while (std::getline(file, line)) { - if(line[0] == '[') { // section - continue; // ignore for now - } - // we cant say comment by only looking at first char anymore int comment_pos = line.find("#"); if(comment_pos > 0) { @@ -102,67 +103,50 @@ void MwIniImporter::merge(strmap &cfg, strmap &ini) { } } -void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini) { - std::vector esmFiles; - std::string baseEsm("Game Files:GameFile"); - std::string esmFile(""); - - strmap::iterator it = ini.begin(); - for(int i=0; it != ini.end(); i++) { - esmFile = baseEsm; - esmFile.append(1,i+'0'); - - it = ini.find(esmFile); - if(it == ini.end()) { - break; - } - - std::cout << "found EMS file: " << it->second << std::endl; - esmFiles.push_back(it->second); - esmFile = ""; - } - - - std::vector bsaFiles; - std::string baseBsa("Archives:Archive "); - std::string bsaFile(""); - - it = ini.begin(); - for(int i=0; it != ini.end(); i++) { - bsaFile = baseBsa; - bsaFile.append(1,i+'0'); - - it = ini.find(bsaFile); - if(it == ini.end()) { - break; - } - - std::cout << "found BSA file: " << it->second << std::endl; - bsaFiles.push_back(it->second); - bsaFile = ""; - } - - if(!esmFiles.empty()) { - cfg.erase("master"); - for(std::vector::iterator it = esmFiles.begin(); it != esmFiles.end(); it++) { - cfg.insert(std::make_pair("master", *it)); - } - } - - if(!bsaFile.empty()) { - cfg.erase("plugin"); - for(std::vector::iterator it = bsaFiles.begin(); it != bsaFiles.end(); it++) { - cfg.insert(std::make_pair("plugin", *it)); - } - } -} - bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { return false; } -void MwIniImporter::writeToFile(std::string file, strmap &cfg) { - boost::iostreams::stream out(file); +void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles) { + std::string baseGameFile("Game Files:GameFile"); + std::string gameFile(""); + + strmap::iterator it = ini.begin(); + for(int i=0; it != ini.end(); i++) { + gameFile = baseGameFile; + gameFile.append(1,i+'0'); + + it = ini.find(gameFile); + if(it == ini.end()) { + break; + } + + std::string filetype(it->second.substr(it->second.length()-4, 3)); + std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); + + if(filetype.compare("esm") == 0) { + esmFiles.push_back(it->second); + } + else if(filetype.compare("esp") == 0) { + espFiles.push_back(it->second); + } + + gameFile = ""; + } +} + +void MwIniImporter::writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles) { + for(std::vector::iterator it=esmFiles.begin(); it != esmFiles.end(); it++) { + out << "master=" << *it << std::endl; + } + for(std::vector::iterator it=espFiles.begin(); it != espFiles.end(); it++) { + out << "plugin=" << *it << std::endl; + } +} + +void MwIniImporter::writeToFile(boost::iostreams::stream &out, strmap &cfg) { + cfg.erase("master"); + cfg.erase("plugin"); for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { out << (it->first) << "=" << (it->second) << std::endl; diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 3c85fd25a2..d7250f5e25 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -1,12 +1,15 @@ #ifndef MWINIIMPORTER_IMPORTER #define MWINIIMPORTER_IMPORTER 1 +#include +#include #include #include +#include #include -typedef std::multimap strmap; +typedef std::map strmap; class MwIniImporter { @@ -16,8 +19,9 @@ class MwIniImporter { strmap loadIniFile(std::string filename); strmap loadCfgFile(std::string filename); void merge(strmap &cfg, strmap &ini); - void importGameFiles(strmap &cfg, strmap &ini); - void writeToFile(std::string file, strmap &cfg); + void importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles); + void writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles); + void writeToFile(boost::iostreams::stream &out, strmap &cfg); private: bool specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini); diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 5eba95961a..7426e71ea0 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -58,21 +58,27 @@ int main(int argc, char *argv[]) { std::cerr << "cfg file does not exist" << std::endl; return -4; } + MwIniImporter importer; importer.setVerbose(vm.count("verbose")); + boost::iostreams::stream file(outputFile); - std::multimapini = importer.loadIniFile(iniFile); - std::multimapcfg = importer.loadCfgFile(cfgFile); + std::mapini = importer.loadIniFile(iniFile); + std::mapcfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); if(vm.count("game-files")) { - importer.importGameFiles(cfg, ini); + std::vector esmFiles; + std::vector espFiles; + + importer.importGameFiles(cfg, ini, esmFiles, espFiles); + importer.writeGameFiles(file, esmFiles, espFiles); } std::cout << "write to: " << outputFile << std::endl; - importer.writeToFile(outputFile, cfg); + importer.writeToFile(file, cfg); return 0; } From ca4ad741517b6d975ed1d6e0df151560582d30d5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 31 Mar 2012 21:29:46 +0200 Subject: [PATCH 78/85] more cleanup --- apps/openmw/mwgui/window_manager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index 1afca1d41a..49b6e644d3 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -180,7 +180,6 @@ void WindowManager::updateVisible() // Mouse is visible whenever we're not in game mode MyGUI::PointerManager::getInstance().setVisible(isGuiMode()); - int eff; switch(mode) { case GM_Game: // If in game mode, don't show anything. @@ -202,16 +201,18 @@ void WindowManager::updateVisible() mCharGen->spawnDialog(mode); break; case GM_Inventory: + { // First, compute the effective set of windows to show. // This is controlled both by what windows the // user has opened/closed (the 'shown' variable) and by what // windows we are allowed to show (the 'allowed' var.) - eff = shown & allowed; + int eff = shown & allowed; // Show the windows we want map -> setVisible( (eff & GW_Map) != 0 ); stats -> setVisible( (eff & GW_Stats) != 0 ); break; + } case GM_Dialogue: dialogueWindow->open(); break; From bdc4c79b4e06450bb2735dbfeaa3b8a347248308 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sat, 31 Mar 2012 21:34:40 +0200 Subject: [PATCH 79/85] Fix for segfault when doing 'coc "seyda neen"'. This is a fix for segfault: ==8683== Process terminating with default action of signal 11 (SIGSEGV) ==8683== Access not within mapped region at address 0x0 ==8683== at 0x59DFE4: MWRender::Animation::handleShapes(std::vector >*, Ogre::Entity*, Ogre::SkeletonInstance*) (animation.cpp:503) ==8683== by 0x5A4ECE: MWRender::Actors::update(float) (actors.cpp:134) ==8683== by 0x5937A9: MWRender::RenderingManager::update(float) (renderingmanager.cpp:168) ==8683== by 0x629AD6: MWWorld::World::update(float) (world.cpp:705) ==8683== by 0x68B022: OMW::Engine::frameRenderingQueued(Ogre::FrameEvent const&) (engine.cpp:157) ==8683== by 0x51F9574: Ogre::Root::_fireFrameRenderingQueued(Ogre::FrameEvent&) (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F964F: Ogre::Root::_fireFrameRenderingQueued() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F9681: Ogre::Root::_updateAllRenderTargets() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F98CF: Ogre::Root::renderOneFrame() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x51F990C: Ogre::Root::startRendering() (in /usr/lib/libOgreMain.so.1.8.0) ==8683== by 0x68A669: OMW::Engine::go() (engine.cpp:408) ==8683== by 0x51CECB: main (main.cpp:254) ==8683== If you believe this happened as a result of a stack ==8683== overflow in your program's main thread (unlikely but ==8683== possible), you can try to increase the size of the ==8683== main thread stack using the --main-stacksize= flag. ==8683== The main thread stack size used in this run was 8388608. when doing 'coc "seyda neen"' when animations are enabled (Animation::animate member variable is set to 1). --- apps/openmw/mwrender/animation.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index f3a8f64d55..fb710443b6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -126,6 +126,11 @@ namespace MWRender{ void Animation::handleShapes(std::vector* allshapes, Ogre::Entity* creaturemodel, Ogre::SkeletonInstance *skel){ shapeNumber = 0; + if (allshapes == NULL || creaturemodel == NULL || skel == NULL) + { + return; + } + std::vector::iterator allshapesiter; for(allshapesiter = allshapes->begin(); allshapesiter != allshapes->end(); allshapesiter++) From 1d596d6c722ac579e527b785ff6b393d8375f3b6 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 22:48:50 +0200 Subject: [PATCH 80/85] use std::map > instead of std::map --- apps/mwiniimporter/importer.cpp | 103 ++++++++++++++++++++------------ apps/mwiniimporter/importer.hpp | 20 +++---- apps/mwiniimporter/main.cpp | 11 +--- 3 files changed, 78 insertions(+), 56 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 937073632d..f7ddb2bf07 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -23,11 +23,11 @@ void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } -strmap MwIniImporter::loadIniFile(std::string filename) { +MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; std::string section(""); - std::map map; + MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); std::string line; @@ -54,16 +54,23 @@ strmap MwIniImporter::loadIniFile(std::string filename) { continue; } - map.insert(std::make_pair(section + ":" + line.substr(0,pos), line.substr(pos+1))); + std::string key(section + ":" + line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); } return map; } -strmap MwIniImporter::loadCfgFile(std::string filename) { +MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - std::map map; + MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); std::string line; @@ -84,34 +91,43 @@ strmap MwIniImporter::loadCfgFile(std::string filename) { continue; } - map.insert(std::make_pair(line.substr(0,pos), line.substr(pos+1))); + std::string key(line.substr(0,pos)); + std::string value(line.substr(pos+1)); + + multistrmap::iterator it; + if((it = map.find(key)) == map.end()) { + map.insert( std::make_pair > (key, std::vector() ) ); + } + map[key].push_back(value); } return map; } -void MwIniImporter::merge(strmap &cfg, strmap &ini) { - strmap::iterator cfgIt; - strmap::iterator iniIt; +void MwIniImporter::merge(multistrmap &cfg, multistrmap &ini) { + multistrmap::iterator cfgIt; + multistrmap::iterator iniIt; for(strmap::iterator it=mMergeMap.begin(); it!=mMergeMap.end(); it++) { if((iniIt = ini.find(it->second)) != ini.end()) { cfg.erase(it->first); if(!this->specialMerge(it->first, it->second, cfg, ini)) { - cfg.insert(std::make_pair(it->first, iniIt->second)); + cfg.insert(std::make_pair >(it->first, iniIt->second)); } } } } -bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini) { +bool MwIniImporter::specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini) { return false; } -void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles) { +void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { + std::vector esmFiles; + std::vector espFiles; std::string baseGameFile("Game Files:GameFile"); std::string gameFile(""); - strmap::iterator it = ini.begin(); + multistrmap::iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { gameFile = baseGameFile; gameFile.append(1,i+'0'); @@ -121,35 +137,48 @@ void MwIniImporter::importGameFiles(strmap &cfg, strmap &ini, std::vectorsecond.substr(it->second.length()-4, 3)); - std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); - - if(filetype.compare("esm") == 0) { - esmFiles.push_back(it->second); - } - else if(filetype.compare("esp") == 0) { - espFiles.push_back(it->second); + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) { + std::string filetype(entry->substr(entry->length()-4, 3)); + std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); + + if(filetype.compare("esm") == 0) { + esmFiles.push_back(*entry); + } + else if(filetype.compare("esp") == 0) { + espFiles.push_back(*entry); + } } gameFile = ""; } -} - -void MwIniImporter::writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles) { - for(std::vector::iterator it=esmFiles.begin(); it != esmFiles.end(); it++) { - out << "master=" << *it << std::endl; - } - for(std::vector::iterator it=espFiles.begin(); it != espFiles.end(); it++) { - out << "plugin=" << *it << std::endl; - } -} - -void MwIniImporter::writeToFile(boost::iostreams::stream &out, strmap &cfg) { - cfg.erase("master"); - cfg.erase("plugin"); - for(strmap::iterator it=cfg.begin(); it != cfg.end(); it++) { - out << (it->first) << "=" << (it->second) << std::endl; + if(!esmFiles.empty()) { + multistrmap::iterator it; + cfg.erase("master"); + cfg.insert( std::make_pair > ("master", std::vector() ) ); + + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { + cfg["master"].push_back(*it); + } + } + + if(!espFiles.empty()) { + multistrmap::iterator it; + cfg.erase("plugin"); + cfg.insert( std::make_pair > ("plugin", std::vector() ) ); + + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { + cfg["plugin"].push_back(*it); + } + } +} + +void MwIniImporter::writeToFile(boost::iostreams::stream &out, multistrmap &cfg) { + + for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) { + for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); entry++) { + out << (it->first) << "=" << (*entry) << std::endl; + } } } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index d7250f5e25..454dc209af 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -8,23 +8,21 @@ #include #include - -typedef std::map strmap; - class MwIniImporter { - public: + typedef std::map strmap; + typedef std::map > multistrmap; + MwIniImporter(); void setVerbose(bool verbose); - strmap loadIniFile(std::string filename); - strmap loadCfgFile(std::string filename); - void merge(strmap &cfg, strmap &ini); - void importGameFiles(strmap &cfg, strmap &ini, std::vector &esmFiles, std::vector &espFiles); - void writeGameFiles(boost::iostreams::stream &out, std::vector &esmFiles, std::vector &espFiles); - void writeToFile(boost::iostreams::stream &out, strmap &cfg); + multistrmap loadIniFile(std::string filename); + multistrmap loadCfgFile(std::string filename); + void merge(multistrmap &cfg, multistrmap &ini); + void importGameFiles(multistrmap &cfg, multistrmap &ini); + void writeToFile(boost::iostreams::stream &out, multistrmap &cfg); private: - bool specialMerge(std::string cfgKey, std::string iniKey, strmap &cfg, strmap &ini); + bool specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini); bool mVerbose; strmap mMergeMap; }; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index 7426e71ea0..9a6e61645d 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -59,22 +59,17 @@ int main(int argc, char *argv[]) { return -4; } - MwIniImporter importer; importer.setVerbose(vm.count("verbose")); boost::iostreams::stream file(outputFile); - std::mapini = importer.loadIniFile(iniFile); - std::mapcfg = importer.loadCfgFile(cfgFile); + MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); + MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); importer.merge(cfg, ini); if(vm.count("game-files")) { - std::vector esmFiles; - std::vector espFiles; - - importer.importGameFiles(cfg, ini, esmFiles, espFiles); - importer.writeGameFiles(file, esmFiles, espFiles); + importer.importGameFiles(cfg, ini); } std::cout << "write to: " << outputFile << std::endl; From a2a7539fd55b2a4480c36575f0814d4180215146 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 31 Mar 2012 23:15:33 +0200 Subject: [PATCH 81/85] fix for more than 10 game files; delete both master and plugin settings if called with --game-files --- apps/mwiniimporter/importer.cpp | 36 +++++++++++++++++---------------- apps/mwiniimporter/importer.hpp | 1 + 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index f7ddb2bf07..a82240a8aa 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include MwIniImporter::MwIniImporter() { const char *map[][2] = @@ -23,6 +24,12 @@ void MwIniImporter::setVerbose(bool verbose) { mVerbose = verbose; } +std::string MwIniImporter::numberToString(int n) { + std::stringstream str; + str << n; + return str.str(); +} + MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; @@ -130,7 +137,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { multistrmap::iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { gameFile = baseGameFile; - gameFile.append(1,i+'0'); + gameFile.append(this->numberToString(i)); it = ini.find(gameFile); if(it == ini.end()) { @@ -152,24 +159,19 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { gameFile = ""; } - if(!esmFiles.empty()) { - multistrmap::iterator it; - cfg.erase("master"); - cfg.insert( std::make_pair > ("master", std::vector() ) ); - - for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { - cfg["master"].push_back(*it); - } + multistrmap::iterator it; + cfg.erase("master"); + cfg.insert( std::make_pair > ("master", std::vector() ) ); + + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { + cfg["master"].push_back(*it); } - if(!espFiles.empty()) { - multistrmap::iterator it; - cfg.erase("plugin"); - cfg.insert( std::make_pair > ("plugin", std::vector() ) ); - - for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { - cfg["plugin"].push_back(*it); - } + cfg.erase("plugin"); + cfg.insert( std::make_pair > ("plugin", std::vector() ) ); + + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { + cfg["plugin"].push_back(*it); } } diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 454dc209af..988f10255e 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -23,6 +23,7 @@ class MwIniImporter { private: bool specialMerge(std::string cfgKey, std::string iniKey, multistrmap &cfg, multistrmap &ini); + std::string numberToString(int n); bool mVerbose; strmap mMergeMap; }; From 8aa4001937834851b83146177cc591ebbc6f0c47 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 1 Apr 2012 10:34:51 +0200 Subject: [PATCH 82/85] compile fix --- apps/mwiniimporter/importer.cpp | 51 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index a82240a8aa..08b05f417e 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -14,7 +14,7 @@ MwIniImporter::MwIniImporter() { { "fps", "General:Show FPS" }, { 0, 0 } }; - + for(int i=0; map[i][0]; i++) { mMergeMap.insert(std::make_pair(map[i][0], map[i][1])); } @@ -32,7 +32,7 @@ std::string MwIniImporter::numberToString(int n) { MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { std::cout << "load ini file: " << filename << std::endl; - + std::string section(""); MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); @@ -46,68 +46,68 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(std::string filename) { } continue; } - + int comment_pos = line.find(";"); if(comment_pos > 0) { line = line.substr(0,comment_pos); } - + if(line.empty()) { continue; } - + int pos = line.find("="); if(pos < 1) { continue; } - + std::string key(section + ":" + line.substr(0,pos)); std::string value(line.substr(pos+1)); - + multistrmap::iterator it; if((it = map.find(key)) == map.end()) { map.insert( std::make_pair > (key, std::vector() ) ); } map[key].push_back(value); } - + return map; } MwIniImporter::multistrmap MwIniImporter::loadCfgFile(std::string filename) { std::cout << "load cfg file: " << filename << std::endl; - + MwIniImporter::multistrmap map; boost::iostreams::streamfile(filename.c_str()); std::string line; while (std::getline(file, line)) { - + // we cant say comment by only looking at first char anymore int comment_pos = line.find("#"); if(comment_pos > 0) { line = line.substr(0,comment_pos); } - + if(line.empty()) { continue; } - + int pos = line.find("="); if(pos < 1) { continue; } - + std::string key(line.substr(0,pos)); std::string value(line.substr(pos+1)); - + multistrmap::iterator it; if((it = map.find(key)) == map.end()) { map.insert( std::make_pair > (key, std::vector() ) ); } map[key].push_back(value); } - + return map; } @@ -138,16 +138,16 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { for(int i=0; it != ini.end(); i++) { gameFile = baseGameFile; gameFile.append(this->numberToString(i)); - + it = ini.find(gameFile); if(it == ini.end()) { break; } - + for(std::vector::iterator entry = it->second.begin(); entry!=it->second.end(); entry++) { std::string filetype(entry->substr(entry->length()-4, 3)); std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower); - + if(filetype.compare("esm") == 0) { esmFiles.push_back(*entry); } @@ -155,33 +155,30 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { espFiles.push_back(*entry); } } - + gameFile = ""; } - - multistrmap::iterator it; + cfg.erase("master"); cfg.insert( std::make_pair > ("master", std::vector() ) ); - + for(std::vector::iterator it=esmFiles.begin(); it!=esmFiles.end(); it++) { cfg["master"].push_back(*it); } - + cfg.erase("plugin"); cfg.insert( std::make_pair > ("plugin", std::vector() ) ); - + for(std::vector::iterator it=espFiles.begin(); it!=espFiles.end(); it++) { cfg["plugin"].push_back(*it); } } void MwIniImporter::writeToFile(boost::iostreams::stream &out, multistrmap &cfg) { - + for(multistrmap::iterator it=cfg.begin(); it != cfg.end(); it++) { for(std::vector::iterator entry=it->second.begin(); entry != it->second.end(); entry++) { out << (it->first) << "=" << (*entry) << std::endl; } } } - - From ee754eda6c5e2377956092ad6a04f02c3367fd1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 1 Apr 2012 15:07:41 +0200 Subject: [PATCH 83/85] don't create sky until entering an exterior cell --- apps/openmw/mwrender/occlusionquery.cpp | 24 +++++++++++++++++------ apps/openmw/mwrender/occlusionquery.hpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 2 ++ apps/openmw/mwrender/sky.cpp | 18 ++++++++++++++++- apps/openmw/mwrender/sky.hpp | 7 ++++++- apps/openmw/mwworld/world.cpp | 4 ++-- 6 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/occlusionquery.cpp b/apps/openmw/mwrender/occlusionquery.cpp index cc3464c646..29cfe33fe3 100644 --- a/apps/openmw/mwrender/occlusionquery.cpp +++ b/apps/openmw/mwrender/occlusionquery.cpp @@ -12,7 +12,8 @@ using namespace Ogre; OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) : mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0), mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false), - mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false) + mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false), + mBBNode(0) { mRendering = renderer; mSunNode = sunNode; @@ -52,7 +53,8 @@ OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNod matQueryVisible->setCullingMode(CULL_NONE); matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE); - mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); + if (sunNode) + mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode(); mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode(); @@ -182,10 +184,13 @@ void OcclusionQuery::update(float duration) if (dist==0) dist = 10000000; dist -= 1000; // bias dist /= 1000.f; - mBBNode->setPosition(mSunNode->getPosition() * dist); - mBBNode->setScale(dist, dist, dist); - mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); - mBBNodeReal->setScale(mBBNode->getScale()); + if (mBBNode) + { + mBBNode->setPosition(mSunNode->getPosition() * dist); + mBBNode->setScale(dist, dist, dist); + mBBNodeReal->setPosition(mBBNode->_getDerivedPosition()); + mBBNodeReal->setScale(mBBNode->getScale()); + } // Stop occlusion queries until we get their information // (may not happen on the same frame they are requested in) @@ -245,6 +250,13 @@ bool OcclusionQuery::occlusionTestPending() return (mQuerySingleObjectRequested || mQuerySingleObjectStarted); } +void OcclusionQuery::setSunNode(Ogre::SceneNode* node) +{ + mSunNode = node; + if (!mBBNode) + mBBNode = node->getParentSceneNode()->createChildSceneNode(); +} + bool OcclusionQuery::getTestResult() { assert( !occlusionTestPending() diff --git a/apps/openmw/mwrender/occlusionquery.hpp b/apps/openmw/mwrender/occlusionquery.hpp index ebdc51311d..b655c8e46b 100644 --- a/apps/openmw/mwrender/occlusionquery.hpp +++ b/apps/openmw/mwrender/occlusionquery.hpp @@ -53,6 +53,8 @@ namespace MWRender float getSunVisibility() const {return mSunVisibility;}; + void setSunNode(Ogre::SceneNode* node); + private: Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery; Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3d715b3d32..bbddd325a1 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -201,6 +201,8 @@ void RenderingManager::skyEnable () { if(mSkyManager) mSkyManager->enable(); + + mOcclusionQuery->setSunNode(mSkyManager->getSunNode()); } void RenderingManager::skyDisable () diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 265008e346..23b44d1f3c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -325,14 +325,17 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen , mSunEnabled(true) , mMasserEnabled(true) , mSecundaEnabled(true) + , mCreated(false) { - mViewport = pCamera->getViewport(); mSceneMgr = pMwRoot->getCreator(); mRootNode = pCamera->getParentSceneNode()->createChildSceneNode(); mRootNode->pitch(Degree(-90)); // convert MW to ogre coordinates mRootNode->setInheritOrientation(false); +} +void SkyManager::create() +{ /// \todo preload all the textures and meshes that are used for sky rendering // Create overlay used for thunderstorm @@ -562,6 +565,8 @@ SkyManager::SkyManager (SceneNode* pMwRoot, Camera* pCamera, MWWorld::Environmen mCloudMaterial->getTechnique(0)->getPass(0)->setSceneBlending(SBT_TRANSPARENT_ALPHA); mCloudMaterial->getTechnique(0)->getPass(0)->createTextureUnitState(""); + + mCreated = true; } SkyManager::~SkyManager() @@ -574,11 +579,13 @@ SkyManager::~SkyManager() int SkyManager::getMasserPhase() const { + if (!mCreated) return 0; return mMasser->getPhaseInt(); } int SkyManager::getSecundaPhase() const { + if (!mCreated) return 0; return mSecunda->getPhaseInt(); } @@ -631,6 +638,9 @@ void SkyManager::update(float duration) void SkyManager::enable() { + if (!mCreated) + create(); + mRootNode->setVisible(true); mEnabled = true; } @@ -654,6 +664,7 @@ void SkyManager::setCloudsOpacity(float opacity) void SkyManager::setWeather(const MWWorld::WeatherResult& weather) { + if (!mCreated) return; if (mClouds != weather.mCloudTexture) { mCloudMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName("textures\\"+weather.mCloudTexture); @@ -750,17 +761,20 @@ void SkyManager::sunDisable() void SkyManager::setSunDirection(const Vector3& direction) { + if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); } void SkyManager::setMasserDirection(const Vector3& direction) { + if (!mCreated) return; mMasser->setPosition(direction); } void SkyManager::setSecundaDirection(const Vector3& direction) { + if (!mCreated) return; mSecunda->setPosition(direction); } @@ -786,6 +800,7 @@ void SkyManager::secundaDisable() void SkyManager::setThunder(const float factor) { + if (!mCreated) return; if (factor > 0.f) { mThunderOverlay->show(); @@ -818,5 +833,6 @@ void SkyManager::setDate(int day, int month) Ogre::SceneNode* SkyManager::getSunNode() { + if (!mCreated) return 0; return mSun->getNode(); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 508af76732..baf5933cbc 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -112,6 +112,9 @@ namespace MWRender void update(float duration); + void create(); + ///< no need to call this, automatically done on first enable() + void enable(); void disable(); @@ -164,8 +167,10 @@ namespace MWRender void setGlare(const float glare); Ogre::Vector3 getRealSunPos(); - + private: + bool mCreated; + MWWorld::Environment* mEnvironment; float mHour; int mDay; diff --git a/apps/openmw/mwworld/world.cpp b/apps/openmw/mwworld/world.cpp index 6f03fa37f5..a48cc7e722 100644 --- a/apps/openmw/mwworld/world.cpp +++ b/apps/openmw/mwworld/world.cpp @@ -146,10 +146,10 @@ namespace MWWorld mRendering->skySetDate (mGlobalVariables->getInt ("day"), mGlobalVariables->getInt ("month")); - mRendering->getSkyManager()->enable(); + mRendering->skyEnable(); } else - mRendering->getSkyManager()->disable(); + mRendering->skyDisable(); } World::World (OEngine::Render::OgreRenderer& renderer, From 9a261a02aa78e1f9edfbb7aac8874d653b814d78 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 1 Apr 2012 15:14:43 +0200 Subject: [PATCH 84/85] changed the cloud movement direction like suggested on the forum --- apps/openmw/mwrender/sky.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 23b44d1f3c..2fdf9b2cd6 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -536,7 +536,7 @@ void SkyManager::create() " uniform float4 emissive \n" ") \n" "{ \n" - " uv += float2(1,0) * time * speed * 0.003; \n" // Scroll in x direction + " uv += float2(0,1) * time * speed * 0.003; \n" // Scroll in y direction " float4 tex = lerp(tex2D(texture, uv), tex2D(secondTexture, uv), transitionFactor); \n" " oColor = color * float4(emissive.xyz,1) * tex * float4(1,1,1,opacity); \n" "}"; From 54ce95cfafb2c525818787f1a52463185eb8278a Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Sun, 1 Apr 2012 17:25:03 +0200 Subject: [PATCH 85/85] Make sure it doesn't find the wrong file --- components/bsa/bsa_archive.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/bsa/bsa_archive.cpp b/components/bsa/bsa_archive.cpp index f4f4b898cf..0e3563b261 100644 --- a/components/bsa/bsa_archive.cpp +++ b/components/bsa/bsa_archive.cpp @@ -41,21 +41,21 @@ struct ciLessBoost : std::binary_function { bool operator() (const std::string & s1, const std::string & s2) const { //case insensitive version of is_less - return lexicographical_compare(s1, s2, boost::algorithm::is_iless()); + return boost::ilexicographical_compare(s1, s2); } }; struct pathComparer { private: - int m_start, m_size; + std::string find; public: - pathComparer(int start, int size) : m_start(start), m_size(size) { } + pathComparer(const std::string& toFind) : find(toFind) { } - bool operator() (const std::string& first, const std::string& other) + bool operator() (const std::string& other) { - return lexicographical_compare(first.substr(m_start,m_size), other.substr(m_start,m_size), boost::algorithm::is_iless()); + return boost::iequals(find, other); } }; @@ -71,9 +71,6 @@ class DirArchive: public Ogre::FileSystemArchive bool findFile(const String& filename, std::string& copy) const { - if (filename.find(".tga") != std::string::npos) - return false; - { String passed = filename; if(filename.at(filename.length() - 1) == '*' || filename.at(filename.length() - 1) == '?' || filename.at(filename.length() - 1) == '<' @@ -116,10 +113,13 @@ class DirArchive: public Ogre::FileSystemArchive current = found->second; } - pathComparer comp(delimiter, copy.size() - delimiter-1); - std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, comp); - if (find != current.end() && !comp(copy, current.front())) + std::vector::iterator find = std::lower_bound(current.begin(), current.end(), copy, ciLessBoost()); + if (find != current.end() && !ciLessBoost()(copy, current.front())) { + if (!boost::iequals(copy, *find)) + if ((find = std::find_if(current.begin(), current.end(), pathComparer(copy))) == current.end()) //\todo Check if this line is actually needed + return false; + copy = *find; return true; }